1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2012, 2013 Zimbra Software, LLC.
  5  * 
  6  * The contents of this file are subject to the Zimbra Public License
  7  * Version 1.4 ("License"); you may not use this file except in
  8  * compliance with the License.  You may obtain a copy of the License at
  9  * http://www.zimbra.com/license.
 10  * 
 11  * Software distributed under the License is distributed on an "AS IS"
 12  * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 13  * ***** END LICENSE BLOCK *****
 14  */
 15 // FILE IS GENERATED BY COMBINING THE SOURCES IN THE "classes" DIRECTORY SO DON'T MODIFY THIS FILE DIRECTLY
 16 (function(win) {
 17 	var whiteSpaceRe = /^\s*|\s*$/g,
 18 		undef, isRegExpBroken = 'B'.replace(/A(.)|B/, '$1') === '$1';
 19 
 20 	var tinymce = {
 21 		majorVersion : '3',
 22 
 23 		minorVersion : '5.4.1',
 24 
 25 		releaseDate : '2012-06-24',
 26 
 27 		_init : function() {
 28 			var t = this, d = document, na = navigator, ua = na.userAgent, i, nl, n, base, p, v;
 29 
 30 			t.isOpera = win.opera && opera.buildNumber;
 31 
 32 			t.isWebKit = /WebKit/.test(ua);
 33 
 34 			t.isIE = !t.isWebKit && !t.isOpera && (/MSIE/gi).test(ua) && (/Explorer/gi).test(na.appName);
 35 
 36 			t.isIE6 = t.isIE && /MSIE [56]/.test(ua);
 37 
 38 			t.isIE7 = t.isIE && /MSIE [7]/.test(ua);
 39 
 40 			t.isIE8 = t.isIE && /MSIE [8]/.test(ua);
 41 
 42 			t.isIE9 = t.isIE && /MSIE [9]/.test(ua);
 43 
 44 			t.isGecko = !t.isWebKit && /Gecko/.test(ua);
 45 
 46 			t.isMac = ua.indexOf('Mac') != -1;
 47 
 48 			t.isAir = /adobeair/i.test(ua);
 49 
 50 			t.isIDevice = /(iPad|iPhone)/.test(ua);
 51 			
 52 			t.isIOS5 = t.isIDevice && ua.match(/AppleWebKit\/(\d*)/)[1]>=534;
 53 
 54 			// TinyMCE .NET webcontrol might be setting the values for TinyMCE
 55 			if (win.tinyMCEPreInit) {
 56 				t.suffix = tinyMCEPreInit.suffix;
 57 				t.baseURL = tinyMCEPreInit.base;
 58 				t.query = tinyMCEPreInit.query;
 59 				return;
 60 			}
 61 
 62 			// Get suffix and base
 63 			t.suffix = '';
 64 
 65 			// If base element found, add that infront of baseURL
 66 			nl = d.getElementsByTagName('base');
 67 			for (i=0; i<nl.length; i++) {
 68 				v = nl[i].href;
 69 				if (v) {
 70 					// Host only value like http://site.com or http://site.com:8008
 71 					if (/^https?:\/\/[^\/]+$/.test(v))
 72 						v += '/';
 73 
 74 					base = v ? v.match(/.*\//)[0] : ''; // Get only directory
 75 				}
 76 			}
 77 
 78 			function getBase(n) {
 79 				if (n.src && /tiny_mce(|_gzip|_jquery|_prototype|_full)(_dev|_src)?.js/.test(n.src)) {
 80 					if (/_(src|dev)\.js/g.test(n.src))
 81 						t.suffix = '_src';
 82 
 83 					if ((p = n.src.indexOf('?')) != -1)
 84 						t.query = n.src.substring(p + 1);
 85 
 86 					t.baseURL = n.src.substring(0, n.src.lastIndexOf('/'));
 87 
 88 					// If path to script is relative and a base href was found add that one infront
 89 					// the src property will always be an absolute one on non IE browsers and IE 8
 90 					// so this logic will basically only be executed on older IE versions
 91 					if (base && t.baseURL.indexOf('://') == -1 && t.baseURL.indexOf('/') !== 0)
 92 						t.baseURL = base + t.baseURL;
 93 
 94 					return t.baseURL;
 95 				}
 96 
 97 				return null;
 98 			};
 99 
100 			// Check document
101 			nl = d.getElementsByTagName('script');
102 			for (i=0; i<nl.length; i++) {
103 				if (getBase(nl[i]))
104 					return;
105 			}
106 
107 			// Check head
108 			n = d.getElementsByTagName('head')[0];
109 			if (n) {
110 				nl = n.getElementsByTagName('script');
111 				for (i=0; i<nl.length; i++) {
112 					if (getBase(nl[i]))
113 						return;
114 				}
115 			}
116 
117 			return;
118 		},
119 
120 		is : function(o, t) {
121 			if (!t)
122 				return o !== undef;
123 
124 			if (t == 'array' && (o.hasOwnProperty && o instanceof Array))
125 				return true;
126 
127 			return typeof(o) == t;
128 		},
129 
130 		makeMap : function(items, delim, map) {
131 			var i;
132 
133 			items = items || [];
134 			delim = delim || ',';
135 
136 			if (typeof(items) == "string")
137 				items = items.split(delim);
138 
139 			map = map || {};
140 
141 			i = items.length;
142 			while (i--)
143 				map[items[i]] = {};
144 
145 			return map;
146 		},
147 
148 		each : function(o, cb, s) {
149 			var n, l;
150 
151 			if (!o)
152 				return 0;
153 
154 			s = s || o;
155 
156 			if (o.length !== undef) {
157 				// Indexed arrays, needed for Safari
158 				for (n=0, l = o.length; n < l; n++) {
159 					if (cb.call(s, o[n], n, o) === false)
160 						return 0;
161 				}
162 			} else {
163 				// Hashtables
164 				for (n in o) {
165 					if (o.hasOwnProperty(n)) {
166 						if (cb.call(s, o[n], n, o) === false)
167 							return 0;
168 					}
169 				}
170 			}
171 
172 			return 1;
173 		},
174 
175 
176 		trim : function(s) {
177 			return (s ? '' + s : '').replace(whiteSpaceRe, '');
178 		},
179 
180 		create : function(s, p, root) {
181 			var t = this, sp, ns, cn, scn, c, de = 0;
182 
183 			// Parse : <prefix> <class>:<super class>
184 			s = /^((static) )?([\w.]+)(:([\w.]+))?/.exec(s);
185 			cn = s[3].match(/(^|\.)(\w+)$/i)[2]; // Class name
186 
187 			// Create namespace for new class
188 			ns = t.createNS(s[3].replace(/\.\w+$/, ''), root);
189 
190 			// Class already exists
191 			if (ns[cn])
192 				return;
193 
194 			// Make pure static class
195 			if (s[2] == 'static') {
196 				ns[cn] = p;
197 
198 				if (this.onCreate)
199 					this.onCreate(s[2], s[3], ns[cn]);
200 
201 				return;
202 			}
203 
204 			// Create default constructor
205 			if (!p[cn]) {
206 				p[cn] = function() {};
207 				de = 1;
208 			}
209 
210 			// Add constructor and methods
211 			ns[cn] = p[cn];
212 			t.extend(ns[cn].prototype, p);
213 
214 			// Extend
215 			if (s[5]) {
216 				sp = t.resolve(s[5]).prototype;
217 				scn = s[5].match(/\.(\w+)$/i)[1]; // Class name
218 
219 				// Extend constructor
220 				c = ns[cn];
221 				if (de) {
222 					// Add passthrough constructor
223 					ns[cn] = function() {
224 						return sp[scn].apply(this, arguments);
225 					};
226 				} else {
227 					// Add inherit constructor
228 					ns[cn] = function() {
229 						this.parent = sp[scn];
230 						return c.apply(this, arguments);
231 					};
232 				}
233 				ns[cn].prototype[cn] = ns[cn];
234 
235 				// Add super methods
236 				t.each(sp, function(f, n) {
237 					ns[cn].prototype[n] = sp[n];
238 				});
239 
240 				// Add overridden methods
241 				t.each(p, function(f, n) {
242 					// Extend methods if needed
243 					if (sp[n]) {
244 						ns[cn].prototype[n] = function() {
245 							this.parent = sp[n];
246 							return f.apply(this, arguments);
247 						};
248 					} else {
249 						if (n != cn)
250 							ns[cn].prototype[n] = f;
251 					}
252 				});
253 			}
254 
255 			// Add static methods
256 			t.each(p['static'], function(f, n) {
257 				ns[cn][n] = f;
258 			});
259 
260 			if (this.onCreate)
261 				this.onCreate(s[2], s[3], ns[cn].prototype);
262 		},
263 
264 		walk : function(o, f, n, s) {
265 			s = s || this;
266 
267 			if (o) {
268 				if (n)
269 					o = o[n];
270 
271 				tinymce.each(o, function(o, i) {
272 					if (f.call(s, o, i, n) === false)
273 						return false;
274 
275 					tinymce.walk(o, f, n, s);
276 				});
277 			}
278 		},
279 
280 		createNS : function(n, o) {
281 			var i, v;
282 
283 			o = o || win;
284 
285 			n = n.split('.');
286 			for (i=0; i<n.length; i++) {
287 				v = n[i];
288 
289 				if (!o[v])
290 					o[v] = {};
291 
292 				o = o[v];
293 			}
294 
295 			return o;
296 		},
297 
298 		resolve : function(n, o) {
299 			var i, l;
300 
301 			o = o || win;
302 
303 			n = n.split('.');
304 			for (i = 0, l = n.length; i < l; i++) {
305 				o = o[n[i]];
306 
307 				if (!o)
308 					break;
309 			}
310 
311 			return o;
312 		},
313 
314 		addUnload : function(f, s) {
315 			var t = this, unload;
316 
317 			unload = function() {
318 				var li = t.unloads, o, n;
319 
320 				if (li) {
321 					// Call unload handlers
322 					for (n in li) {
323 						o = li[n];
324 
325 						if (o && o.func)
326 							o.func.call(o.scope, 1); // Send in one arg to distinct unload and user destroy
327 					}
328 
329 					// Detach unload function
330 					if (win.detachEvent) {
331 						win.detachEvent('onbeforeunload', fakeUnload);
332 						win.detachEvent('onunload', unload);
333 					} else if (win.removeEventListener)
334 						win.removeEventListener('unload', unload, false);
335 
336 					// Destroy references
337 					t.unloads = o = li = w = unload = 0;
338 
339 					// Run garbarge collector on IE
340 					if (win.CollectGarbage)
341 						CollectGarbage();
342 				}
343 			};
344 
345 			function fakeUnload() {
346 				var d = document;
347 
348 				function stop() {
349 					// Prevent memory leak
350 					d.detachEvent('onstop', stop);
351 
352 					// Call unload handler
353 					if (unload)
354 						unload();
355 
356 					d = 0;
357 				};
358 
359 				// Is there things still loading, then do some magic
360 				if (d.readyState == 'interactive') {
361 					// Fire unload when the currently loading page is stopped
362 					if (d)
363 						d.attachEvent('onstop', stop);
364 
365 					// Remove onstop listener after a while to prevent the unload function
366 					// to execute if the user presses cancel in an onbeforeunload
367 					// confirm dialog and then presses the browser stop button
368 					win.setTimeout(function() {
369 						if (d)
370 							d.detachEvent('onstop', stop);
371 					}, 0);
372 				}
373 			};
374 
375 			f = {func : f, scope : s || this};
376 
377 			if (!t.unloads) {
378 				// Attach unload handler
379 				if (win.attachEvent) {
380 					win.attachEvent('onunload', unload);
381 					win.attachEvent('onbeforeunload', fakeUnload);
382 				} else if (win.addEventListener)
383 					win.addEventListener('unload', unload, false);
384 
385 				// Setup initial unload handler array
386 				t.unloads = [f];
387 			} else
388 				t.unloads.push(f);
389 
390 			return f;
391 		},
392 
393 		removeUnload : function(f) {
394 			var u = this.unloads, r = null;
395 
396 			tinymce.each(u, function(o, i) {
397 				if (o && o.func == f) {
398 					u.splice(i, 1);
399 					r = f;
400 					return false;
401 				}
402 			});
403 
404 			return r;
405 		},
406 
407 		explode : function(s, d) {
408 			if (!s || tinymce.is(s, 'array')) {
409 				return s;
410 			}
411 
412 			return tinymce.map(s.split(d || ','), tinymce.trim);
413 		},
414 
415 		_addVer : function(u) {
416 			var v;
417 
418 			if (!this.query)
419 				return u;
420 
421 			v = (u.indexOf('?') == -1 ? '?' : '&') + this.query;
422 
423 			if (u.indexOf('#') == -1)
424 				return u + v;
425 
426 			return u.replace('#', v + '#');
427 		},
428 
429 		// Fix function for IE 9 where regexps isn't working correctly
430 		// Todo: remove me once MS fixes the bug
431 		_replace : function(find, replace, str) {
432 			// On IE9 we have to fake $x replacement
433 			if (isRegExpBroken) {
434 				return str.replace(find, function() {
435 					var val = replace, args = arguments, i;
436 
437 					for (i = 0; i < args.length - 2; i++) {
438 						if (args[i] === undef) {
439 							val = val.replace(new RegExp('\\$' + i, 'g'), '');
440 						} else {
441 							val = val.replace(new RegExp('\\$' + i, 'g'), args[i]);
442 						}
443 					}
444 
445 					return val;
446 				});
447 			}
448 
449 			return str.replace(find, replace);
450 		}
451 
452 		};
453 
454 	// Initialize the API
455 	tinymce._init();
456 
457 	// Expose tinymce namespace to the global namespace (window)
458 	win.tinymce = win.tinyMCE = tinymce;
459 
460 	// Describe the different namespaces
461 
462 	})(window);
463 
464 
465 (function($, tinymce) {
466 	var is = tinymce.is, attrRegExp = /^(href|src|style)$/i, undef;
467 
468 	// jQuery is undefined
469 	if (!$ && window.console) {
470 		return console.log("Load jQuery first!");
471 	}
472 
473 	// Stick jQuery into the tinymce namespace
474 	tinymce.$ = $;
475 
476 	// Setup adapter
477 	tinymce.adapter = {
478 		patchEditor : function(editor) {
479 			var fn = $.fn;
480 
481 			// Adapt the css function to make sure that the data-mce-style
482 			// attribute gets updated with the new style information
483 			function css(name, value) {
484 				var self = this;
485 
486 				// Remove data-mce-style when set operation occurs
487 				if (value)
488 					self.removeAttr('data-mce-style');
489 
490 				return fn.css.apply(self, arguments);
491 			};
492 
493 			// Apapt the attr function to make sure that it uses the data-mce- prefixed variants
494 			function attr(name, value) {
495 				var self = this;
496 
497 				// Update/retrive data-mce- attribute variants
498 				if (attrRegExp.test(name)) {
499 					if (value !== undef) {
500 						// Use TinyMCE behavior when setting the specifc attributes
501 						self.each(function(i, node) {
502 							editor.dom.setAttrib(node, name, value);
503 						});
504 
505 						return self;
506 					} else
507 						return self.attr('data-mce-' + name);
508 				}
509 
510 				// Default behavior
511 				return fn.attr.apply(self, arguments);
512 			};
513 
514 			// Patch various jQuery functions to handle tinymce specific attribute and content behavior
515 			// we don't patch the jQuery.fn directly since it will most likely break compatibility
516 			// with other jQuery logic on the page. Only instances created by TinyMCE should be patched.
517 			function patch(jq) {
518 				// Patch some functions, only patch the object once
519 				if (jq.css !== css) {
520 					// Patch css/attr to use the data-mce- prefixed attribute variants
521 					jq.css = css;
522 					jq.attr = attr;
523 
524 					jq.tinymce = editor;
525 
526 					// Each pushed jQuery instance needs to be patched
527 					// as well for example when traversing the DOM
528 					jq.pushStack = function() {
529 						return patch(fn.pushStack.apply(this, arguments));
530 					};
531 				}
532 
533 				return jq;
534 			};
535 
536 			// Add a $ function on each editor instance this one is scoped for the editor document object
537 			// this way you can do chaining like this tinymce.get(0).$('p').append('text').css('color', 'red');
538 			editor.$ = function(selector, scope) {
539 				var doc = editor.getDoc();
540 
541 				return patch($(selector || doc, doc || scope));
542 			};
543 		}
544 	};
545 
546 	// Patch in core NS functions
547 	tinymce.extend = $.extend;
548 	tinymce.extend(tinymce, {
549 		map : $.map,
550 		grep : function(a, f) {return $.grep(a, f || function(){return 1;});},
551 		inArray : function(a, v) {return $.inArray(v, a || []);}
552 
553 		/* Didn't iterate stylesheets
554 		each : function(o, cb, s) {
555 			if (!o)
556 				return 0;
557 
558 			var r = 1;
559 
560 			$.each(o, function(nr, el){
561 				if (cb.call(s, el, nr, o) === false) {
562 					r = 0;
563 					return false;
564 				}
565 			});
566 
567 			return r;
568 		}*/
569 	});
570 
571 	// Patch in functions in various clases
572 	// Add a "#ifndefjquery" statement around each core API function you add below
573 	var patches = {
574 		'tinymce.dom.DOMUtils' : {
575 			/*
576 			addClass : function(e, c) {
577 				if (is(e, 'array') && is(e[0], 'string'))
578 					e = e.join(',#');
579 				return (e && $(is(e, 'string') ? '#' + e : e)
580 					.addClass(c)
581 					.attr('class')) || false;
582 			},
583 
584 			hasClass : function(n, c) {
585 				return $(is(n, 'string') ? '#' + n : n).hasClass(c);
586 			},
587 
588 			removeClass : function(e, c) {
589 				if (!e)
590 					return false;
591 
592 				var r = [];
593 
594 				$(is(e, 'string') ? '#' + e : e)
595 					.removeClass(c)
596 					.each(function(){
597 						r.push(this.className);
598 					});
599 
600 				return r.length == 1 ? r[0] : r;
601 			},
602 			*/
603 
604 			select : function(pattern, scope) {
605 				var t = this;
606 
607 				return $.find(pattern, t.get(scope) || t.get(t.settings.root_element) || t.doc, []);
608 			},
609 
610 			is : function(n, patt) {
611 				return $(this.get(n)).is(patt);
612 			}
613 
614 			/*
615 			show : function(e) {
616 				if (is(e, 'array') && is(e[0], 'string'))
617 					e = e.join(',#');
618 
619 				$(is(e, 'string') ? '#' + e : e).css('display', 'block');
620 			},
621 
622 			hide : function(e) {
623 				if (is(e, 'array') && is(e[0], 'string'))
624 					e = e.join(',#');
625 
626 				$(is(e, 'string') ? '#' + e : e).css('display', 'none');
627 			},
628 
629 			isHidden : function(e) {
630 				return $(is(e, 'string') ? '#' + e : e).is(':hidden');
631 			},
632 
633 			insertAfter : function(n, e) {
634 				return $(is(e, 'string') ? '#' + e : e).after(n);
635 			},
636 
637 			replace : function(o, n, k) {
638 				n = $(is(n, 'string') ? '#' + n : n);
639 
640 				if (k)
641 					n.children().appendTo(o);
642 
643 				n.replaceWith(o);
644 			},
645 
646 			setStyle : function(n, na, v) {
647 				if (is(n, 'array') && is(n[0], 'string'))
648 					n = n.join(',#');
649 
650 				$(is(n, 'string') ? '#' + n : n).css(na, v);
651 			},
652 
653 			getStyle : function(n, na, c) {
654 				return $(is(n, 'string') ? '#' + n : n).css(na);
655 			},
656 
657 			setStyles : function(e, o) {
658 				if (is(e, 'array') && is(e[0], 'string'))
659 					e = e.join(',#');
660 				$(is(e, 'string') ? '#' + e : e).css(o);
661 			},
662 
663 			setAttrib : function(e, n, v) {
664 				var t = this, s = t.settings;
665 
666 				if (is(e, 'array') && is(e[0], 'string'))
667 					e = e.join(',#');
668 
669 				e = $(is(e, 'string') ? '#' + e : e);
670 
671 				switch (n) {
672 					case "style":
673 						e.each(function(i, v){
674 							if (s.keep_values)
675 								$(v).attr('data-mce-style', v);
676 
677 							v.style.cssText = v;
678 						});
679 						break;
680 
681 					case "class":
682 						e.each(function(){
683 							this.className = v;
684 						});
685 						break;
686 
687 					case "src":
688 					case "href":
689 						e.each(function(i, v){
690 							if (s.keep_values) {
691 								if (s.url_converter)
692 									v = s.url_converter.call(s.url_converter_scope || t, v, n, v);
693 
694 								t.setAttrib(v, 'data-mce-' + n, v);
695 							}
696 						});
697 
698 						break;
699 				}
700 
701 				if (v !== null && v.length !== 0)
702 					e.attr(n, '' + v);
703 				else
704 					e.removeAttr(n);
705 			},
706 
707 			setAttribs : function(e, o) {
708 				var t = this;
709 
710 				$.each(o, function(n, v){
711 					t.setAttrib(e,n,v);
712 				});
713 			}
714 			*/
715 		}
716 
717 /*
718 		'tinymce.dom.Event' : {
719 			add : function (o, n, f, s) {
720 				var lo, cb;
721 
722 				cb = function(e) {
723 					e.target = e.target || this;
724 					f.call(s || this, e);
725 				};
726 
727 				if (is(o, 'array') && is(o[0], 'string'))
728 					o = o.join(',#');
729 				o = $(is(o, 'string') ? '#' + o : o);
730 				if (n == 'init') {
731 					o.ready(cb, s);
732 				} else {
733 					if (s) {
734 						o.bind(n, s, cb);
735 					} else {
736 						o.bind(n, cb);
737 					}
738 				}
739 
740 				lo = this._jqLookup || (this._jqLookup = []);
741 				lo.push({func : f, cfunc : cb});
742 
743 				return cb;
744 			},
745 
746 			remove : function(o, n, f) {
747 				// Find cfunc
748 				$(this._jqLookup).each(function() {
749 					if (this.func === f)
750 						f = this.cfunc;
751 				});
752 
753 				if (is(o, 'array') && is(o[0], 'string'))
754 					o = o.join(',#');
755 
756 				$(is(o, 'string') ? '#' + o : o).unbind(n,f);
757 
758 				return true;
759 			}
760 		}
761 */
762 	};
763 
764 	// Patch functions after a class is created
765 	tinymce.onCreate = function(ty, c, p) {
766 		tinymce.extend(p, patches[c]);
767 	};
768 })(window.jQuery, tinymce);
769 
770 
771 
772 tinymce.create('tinymce.util.Dispatcher', {
773 	scope : null,
774 	listeners : null,
775 	inDispatch: false,
776 
777 	Dispatcher : function(scope) {
778 		this.scope = scope || this;
779 		this.listeners = [];
780 	},
781 
782 	add : function(callback, scope) {
783 		this.listeners.push({cb : callback, scope : scope || this.scope});
784 
785 		return callback;
786 	},
787 
788 	addToTop : function(callback, scope) {
789 		var self = this, listener = {cb : callback, scope : scope || self.scope};
790 
791 		// Create new listeners if addToTop is executed in a dispatch loop
792 		if (self.inDispatch) {
793 			self.listeners = [listener].concat(self.listeners);
794 		} else {
795 			self.listeners.unshift(listener);
796 		}
797 
798 		return callback;
799 	},
800 
801 	remove : function(callback) {
802 		var listeners = this.listeners, output = null;
803 
804 		tinymce.each(listeners, function(listener, i) {
805 			if (callback == listener.cb) {
806 				output = listener;
807 				listeners.splice(i, 1);
808 				return false;
809 			}
810 		});
811 
812 		return output;
813 	},
814 
815 	dispatch : function() {
816 		var self = this, returnValue, args = arguments, i, listeners = self.listeners, listener;
817 
818 		self.inDispatch = true;
819 		
820 		// Needs to be a real loop since the listener count might change while looping
821 		// And this is also more efficient
822 		for (i = 0; i < listeners.length; i++) {
823 			listener = listeners[i];
824 			returnValue = listener.cb.apply(listener.scope, args.length > 0 ? args : [listener.scope]);
825 
826 			if (returnValue === false)
827 				break;
828 		}
829 
830 		self.inDispatch = false;
831 
832 		return returnValue;
833 	}
834 
835 	});
836 
837 (function() {
838 	var each = tinymce.each;
839 
840 	tinymce.create('tinymce.util.URI', {
841 		URI : function(u, s) {
842 			var t = this, o, a, b, base_url;
843 
844 			// Trim whitespace
845 			u = tinymce.trim(u);
846 
847 			// Default settings
848 			s = t.settings = s || {};
849 
850 			// Strange app protocol that isn't http/https or local anchor
851 			// For example: mailto,skype,tel etc.
852 			if (/^([\w\-]+):([^\/]{2})/i.test(u) || /^\s*#/.test(u)) {
853 				t.source = u;
854 				return;
855 			}
856 
857 			// Absolute path with no host, fake host and protocol
858 			if (u.indexOf('/') === 0 && u.indexOf('//') !== 0)
859 				u = (s.base_uri ? s.base_uri.protocol || 'http' : 'http') + '://mce_host' + u;
860 
861 			// Relative path http:// or protocol relative //path
862 			if (!/^[\w\-]*:?\/\//.test(u)) {
863 				base_url = s.base_uri ? s.base_uri.path : new tinymce.util.URI(location.href).directory;
864 				u = ((s.base_uri && s.base_uri.protocol) || 'http') + '://mce_host' + t.toAbsPath(base_url, u);
865 			}
866 
867 			// Parse URL (Credits goes to Steave, http://blog.stevenlevithan.com/archives/parseuri)
868 			u = u.replace(/@@/g, '(mce_at)'); // Zope 3 workaround, they use @@something
869 			u = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(u);
870 			each(["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"], function(v, i) {
871 				var s = u[i];
872 
873 				// Zope 3 workaround, they use @@something
874 				if (s)
875 					s = s.replace(/\(mce_at\)/g, '@@');
876 
877 				t[v] = s;
878 			});
879 
880 			b = s.base_uri;
881 			if (b) {
882 				if (!t.protocol)
883 					t.protocol = b.protocol;
884 
885 				if (!t.userInfo)
886 					t.userInfo = b.userInfo;
887 
888 				if (!t.port && t.host === 'mce_host')
889 					t.port = b.port;
890 
891 				if (!t.host || t.host === 'mce_host')
892 					t.host = b.host;
893 
894 				t.source = '';
895 			}
896 
897 			//t.path = t.path || '/';
898 		},
899 
900 		setPath : function(p) {
901 			var t = this;
902 
903 			p = /^(.*?)\/?(\w+)?$/.exec(p);
904 
905 			// Update path parts
906 			t.path = p[0];
907 			t.directory = p[1];
908 			t.file = p[2];
909 
910 			// Rebuild source
911 			t.source = '';
912 			t.getURI();
913 		},
914 
915 		toRelative : function(u) {
916 			var t = this, o;
917 
918 			if (u === "./")
919 				return u;
920 
921 			u = new tinymce.util.URI(u, {base_uri : t});
922 
923 			// Not on same domain/port or protocol
924 			if ((u.host != 'mce_host' && t.host != u.host && u.host) || t.port != u.port || t.protocol != u.protocol)
925 				return u.getURI();
926 
927 			var tu = t.getURI(), uu = u.getURI();
928 			
929 			// Allow usage of the base_uri when relative_urls = true
930 			if(tu == uu || (tu.charAt(tu.length - 1) == "/" && tu.substr(0, tu.length - 1) == uu))
931 				return tu;
932 
933 			o = t.toRelPath(t.path, u.path);
934 
935 			// Add query
936 			if (u.query)
937 				o += '?' + u.query;
938 
939 			// Add anchor
940 			if (u.anchor)
941 				o += '#' + u.anchor;
942 
943 			return o;
944 		},
945 	
946 		toAbsolute : function(u, nh) {
947 			u = new tinymce.util.URI(u, {base_uri : this});
948 
949 			return u.getURI(this.host == u.host && this.protocol == u.protocol ? nh : 0);
950 		},
951 
952 		toRelPath : function(base, path) {
953 			var items, bp = 0, out = '', i, l;
954 
955 			// Split the paths
956 			base = base.substring(0, base.lastIndexOf('/'));
957 			base = base.split('/');
958 			items = path.split('/');
959 
960 			if (base.length >= items.length) {
961 				for (i = 0, l = base.length; i < l; i++) {
962 					if (i >= items.length || base[i] != items[i]) {
963 						bp = i + 1;
964 						break;
965 					}
966 				}
967 			}
968 
969 			if (base.length < items.length) {
970 				for (i = 0, l = items.length; i < l; i++) {
971 					if (i >= base.length || base[i] != items[i]) {
972 						bp = i + 1;
973 						break;
974 					}
975 				}
976 			}
977 
978 			if (bp === 1)
979 				return path;
980 
981 			for (i = 0, l = base.length - (bp - 1); i < l; i++)
982 				out += "../";
983 
984 			for (i = bp - 1, l = items.length; i < l; i++) {
985 				if (i != bp - 1)
986 					out += "/" + items[i];
987 				else
988 					out += items[i];
989 			}
990 
991 			return out;
992 		},
993 
994 		toAbsPath : function(base, path) {
995 			var i, nb = 0, o = [], tr, outPath;
996 
997 			// Split paths
998 			tr = /\/$/.test(path) ? '/' : '';
999 			base = base.split('/');
1000 			path = path.split('/');
1001 
1002 			// Remove empty chunks
1003 			each(base, function(k) {
1004 				if (k)
1005 					o.push(k);
1006 			});
1007 
1008 			base = o;
1009 
1010 			// Merge relURLParts chunks
1011 			for (i = path.length - 1, o = []; i >= 0; i--) {
1012 				// Ignore empty or .
1013 				if (path[i].length === 0 || path[i] === ".")
1014 					continue;
1015 
1016 				// Is parent
1017 				if (path[i] === '..') {
1018 					nb++;
1019 					continue;
1020 				}
1021 
1022 				// Move up
1023 				if (nb > 0) {
1024 					nb--;
1025 					continue;
1026 				}
1027 
1028 				o.push(path[i]);
1029 			}
1030 
1031 			i = base.length - nb;
1032 
1033 			// If /a/b/c or /
1034 			if (i <= 0)
1035 				outPath = o.reverse().join('/');
1036 			else
1037 				outPath = base.slice(0, i).join('/') + '/' + o.reverse().join('/');
1038 
1039 			// Add front / if it's needed
1040 			if (outPath.indexOf('/') !== 0)
1041 				outPath = '/' + outPath;
1042 
1043 			// Add traling / if it's needed
1044 			if (tr && outPath.lastIndexOf('/') !== outPath.length - 1)
1045 				outPath += tr;
1046 
1047 			return outPath;
1048 		},
1049 
1050 		getURI : function(nh) {
1051 			var s, t = this;
1052 
1053 			// Rebuild source
1054 			if (!t.source || nh) {
1055 				s = '';
1056 
1057 				if (!nh) {
1058 					if (t.protocol)
1059 						s += t.protocol + '://';
1060 
1061 					if (t.userInfo)
1062 						s += t.userInfo + '@';
1063 
1064 					if (t.host)
1065 						s += t.host;
1066 
1067 					if (t.port)
1068 						s += ':' + t.port;
1069 				}
1070 
1071 				if (t.path)
1072 					s += t.path;
1073 
1074 				if (t.query)
1075 					s += '?' + t.query;
1076 
1077 				if (t.anchor)
1078 					s += '#' + t.anchor;
1079 
1080 				t.source = s;
1081 			}
1082 
1083 			return t.source;
1084 		}
1085 	});
1086 })();
1087 
1088 (function() {
1089 	var each = tinymce.each;
1090 
1091 	tinymce.create('static tinymce.util.Cookie', {
1092 		getHash : function(n) {
1093 			var v = this.get(n), h;
1094 
1095 			if (v) {
1096 				each(v.split('&'), function(v) {
1097 					v = v.split('=');
1098 					h = h || {};
1099 					h[unescape(v[0])] = unescape(v[1]);
1100 				});
1101 			}
1102 
1103 			return h;
1104 		},
1105 
1106 		setHash : function(n, v, e, p, d, s) {
1107 			var o = '';
1108 
1109 			each(v, function(v, k) {
1110 				o += (!o ? '' : '&') + escape(k) + '=' + escape(v);
1111 			});
1112 
1113 			this.set(n, o, e, p, d, s);
1114 		},
1115 
1116 		get : function(n) {
1117 			var c = document.cookie, e, p = n + "=", b;
1118 
1119 			// Strict mode
1120 			if (!c)
1121 				return;
1122 
1123 			b = c.indexOf("; " + p);
1124 
1125 			if (b == -1) {
1126 				b = c.indexOf(p);
1127 
1128 				if (b !== 0)
1129 					return null;
1130 			} else
1131 				b += 2;
1132 
1133 			e = c.indexOf(";", b);
1134 
1135 			if (e == -1)
1136 				e = c.length;
1137 
1138 			return unescape(c.substring(b + p.length, e));
1139 		},
1140 
1141 		set : function(n, v, e, p, d, s) {
1142 			document.cookie = n + "=" + escape(v) +
1143 				((e) ? "; expires=" + e.toGMTString() : "") +
1144 				((p) ? "; path=" + escape(p) : "") +
1145 				((d) ? "; domain=" + d : "") +
1146 				((s) ? "; secure" : "");
1147 		},
1148 
1149 		remove : function(name, path, domain) {
1150 			var date = new Date();
1151 
1152 			date.setTime(date.getTime() - 1000);
1153 
1154 			this.set(name, '', date, path, domain);
1155 		}
1156 	});
1157 })();
1158 
1159 (function() {
1160 	function serialize(o, quote) {
1161 		var i, v, t, name;
1162 
1163 		quote = quote || '"';
1164 
1165 		if (o == null)
1166 			return 'null';
1167 
1168 		t = typeof o;
1169 
1170 		if (t == 'string') {
1171 			v = '\bb\tt\nn\ff\rr\""\'\'\\\\';
1172 
1173 			return quote + o.replace(/([\u0080-\uFFFF\x00-\x1f\"\'\\])/g, function(a, b) {
1174 				// Make sure single quotes never get encoded inside double quotes for JSON compatibility
1175 				if (quote === '"' && a === "'")
1176 					return a;
1177 
1178 				i = v.indexOf(b);
1179 
1180 				if (i + 1)
1181 					return '\\' + v.charAt(i + 1);
1182 
1183 				a = b.charCodeAt().toString(16);
1184 
1185 				return '\\u' + '0000'.substring(a.length) + a;
1186 			}) + quote;
1187 		}
1188 
1189 		if (t == 'object') {
1190 			if (o.hasOwnProperty && o instanceof Array) {
1191 					for (i=0, v = '['; i<o.length; i++)
1192 						v += (i > 0 ? ',' : '') + serialize(o[i], quote);
1193 
1194 					return v + ']';
1195 				}
1196 
1197 				v = '{';
1198 
1199 				for (name in o) {
1200 					if (o.hasOwnProperty(name)) {
1201 						v += typeof o[name] != 'function' ? (v.length > 1 ? ',' + quote : quote) + name + quote +':' + serialize(o[name], quote) : '';
1202 					}
1203 				}
1204 
1205 				return v + '}';
1206 		}
1207 
1208 		return '' + o;
1209 	};
1210 
1211 	tinymce.util.JSON = {
1212 		serialize: serialize,
1213 
1214 		parse: function(s) {
1215 			try {
1216 				return eval('(' + s + ')');
1217 			} catch (ex) {
1218 				// Ignore
1219 			}
1220 		}
1221 
1222 		};
1223 })();
1224 
1225 tinymce.create('static tinymce.util.XHR', {
1226 	send : function(o) {
1227 		var x, t, w = window, c = 0;
1228 
1229 		function ready() {
1230 			if (!o.async || x.readyState == 4 || c++ > 10000) {
1231 				if (o.success && c < 10000 && x.status == 200)
1232 					o.success.call(o.success_scope, '' + x.responseText, x, o);
1233 				else if (o.error)
1234 					o.error.call(o.error_scope, c > 10000 ? 'TIMED_OUT' : 'GENERAL', x, o);
1235 
1236 				x = null;
1237 			} else
1238 				w.setTimeout(ready, 10);
1239 		};
1240 
1241 		// Default settings
1242 		o.scope = o.scope || this;
1243 		o.success_scope = o.success_scope || o.scope;
1244 		o.error_scope = o.error_scope || o.scope;
1245 		o.async = o.async === false ? false : true;
1246 		o.data = o.data || '';
1247 
1248 		function get(s) {
1249 			x = 0;
1250 
1251 			try {
1252 				x = new ActiveXObject(s);
1253 			} catch (ex) {
1254 			}
1255 
1256 			return x;
1257 		};
1258 
1259 		x = w.XMLHttpRequest ? new XMLHttpRequest() : get('Microsoft.XMLHTTP') || get('Msxml2.XMLHTTP');
1260 
1261 		if (x) {
1262 			if (x.overrideMimeType)
1263 				x.overrideMimeType(o.content_type);
1264 
1265 			x.open(o.type || (o.data ? 'POST' : 'GET'), o.url, o.async);
1266 
1267 			if (o.content_type)
1268 				x.setRequestHeader('Content-Type', o.content_type);
1269 
1270 			x.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
1271 
1272 			x.send(o.data);
1273 
1274 			// Syncronous request
1275 			if (!o.async)
1276 				return ready();
1277 
1278 			// Wait for response, onReadyStateChange can not be used since it leaks memory in IE
1279 			t = w.setTimeout(ready, 10);
1280 		}
1281 	}
1282 });
1283 
1284 (function() {
1285 	var extend = tinymce.extend, JSON = tinymce.util.JSON, XHR = tinymce.util.XHR;
1286 
1287 	tinymce.create('tinymce.util.JSONRequest', {
1288 		JSONRequest : function(s) {
1289 			this.settings = extend({
1290 			}, s);
1291 			this.count = 0;
1292 		},
1293 
1294 		send : function(o) {
1295 			var ecb = o.error, scb = o.success;
1296 
1297 			o = extend(this.settings, o);
1298 
1299 			o.success = function(c, x) {
1300 				c = JSON.parse(c);
1301 
1302 				if (typeof(c) == 'undefined') {
1303 					c = {
1304 						error : 'JSON Parse error.'
1305 					};
1306 				}
1307 
1308 				if (c.error)
1309 					ecb.call(o.error_scope || o.scope, c.error, x);
1310 				else
1311 					scb.call(o.success_scope || o.scope, c.result);
1312 			};
1313 
1314 			o.error = function(ty, x) {
1315 				if (ecb)
1316 					ecb.call(o.error_scope || o.scope, ty, x);
1317 			};
1318 
1319 			o.data = JSON.serialize({
1320 				id : o.id || 'c' + (this.count++),
1321 				method : o.method,
1322 				params : o.params
1323 			});
1324 
1325 			// JSON content type for Ruby on rails. Bug: #1883287
1326 			o.content_type = 'application/json';
1327 
1328 			XHR.send(o);
1329 		},
1330 
1331 		'static' : {
1332 			sendRPC : function(o) {
1333 				return new tinymce.util.JSONRequest().send(o);
1334 			}
1335 		}
1336 	});
1337 }());
1338 (function(tinymce){
1339 	tinymce.VK = {
1340 		BACKSPACE: 8,
1341 		DELETE: 46,
1342 		DOWN: 40,
1343 		ENTER: 13,
1344 		LEFT: 37,
1345 		RIGHT: 39,
1346 		SPACEBAR: 32,
1347 		TAB: 9,
1348 		UP: 38,
1349 
1350 		modifierPressed: function (e) {
1351 			return e.shiftKey || e.ctrlKey || e.altKey;
1352 		},
1353 
1354 		metaKeyPressed: function(e) {
1355 			return tinymce.isMac ? e.metaKey : e.ctrlKey;
1356 		}
1357 	};
1358 })(tinymce);
1359 
1360 tinymce.util.Quirks = function(editor) {
1361 	var VK = tinymce.VK, BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, settings = editor.settings;
1362 
1363 	function setEditorCommandState(cmd, state) {
1364 		try {
1365 			editor.getDoc().execCommand(cmd, false, state);
1366 		} catch (ex) {
1367 			// Ignore
1368 		}
1369 	}
1370 
1371 	function getDocumentMode() {
1372 		var documentMode = editor.getDoc().documentMode;
1373 
1374 		return documentMode ? documentMode : 6;
1375 	};
1376 
1377 	function cleanupStylesWhenDeleting() {
1378 		function removeMergedFormatSpans(isDelete) {
1379 			var rng, blockElm, node, clonedSpan;
1380 
1381 			rng = selection.getRng();
1382 
1383 			// Find root block
1384 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1385 
1386 			// On delete clone the root span of the next block element
1387 			if (isDelete)
1388 				blockElm = dom.getNext(blockElm, dom.isBlock);
1389 
1390 			// Locate root span element and clone it since it would otherwise get merged by the "apple-style-span" on delete/backspace
1391 			if (blockElm) {
1392 				node = blockElm.firstChild;
1393 
1394 				// Ignore empty text nodes
1395 				while (node && node.nodeType == 3 && node.nodeValue.length === 0)
1396 					node = node.nextSibling;
1397 
1398 				if (node && node.nodeName === 'SPAN') {
1399 					clonedSpan = node.cloneNode(false);
1400 				}
1401 			}
1402 
1403 			// Do the backspace/delete action
1404 			editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1405 
1406 			// Find all odd apple-style-spans
1407 			blockElm = dom.getParent(rng.startContainer, dom.isBlock);
1408 			tinymce.each(dom.select('span.Apple-style-span,font.Apple-style-span', blockElm), function(span) {
1409 				var bm = selection.getBookmark();
1410 
1411 				if (clonedSpan) {
1412 					dom.replace(clonedSpan.cloneNode(false), span, true);
1413 				} else {
1414 					dom.remove(span, true);
1415 				}
1416 
1417 				// Restore the selection
1418 				selection.moveToBookmark(bm);
1419 			});
1420 		};
1421 
1422 		editor.onKeyDown.add(function(editor, e) {
1423 			var isDelete;
1424 
1425 			isDelete = e.keyCode == DELETE;
1426 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1427 				e.preventDefault();
1428 				removeMergedFormatSpans(isDelete);
1429 			}
1430 		});
1431 
1432 		editor.addCommand('Delete', function() {removeMergedFormatSpans();});
1433 	};
1434 	
1435 	function emptyEditorWhenDeleting() {
1436 		function serializeRng(rng) {
1437 			var body = dom.create("body");
1438 			var contents = rng.cloneContents();
1439 			body.appendChild(contents);
1440 			return selection.serializer.serialize(body, {format: 'html'});
1441 		}
1442 
1443 		function allContentsSelected(rng) {
1444 			var selection = serializeRng(rng);
1445 
1446 			var allRng = dom.createRng();
1447 			allRng.selectNode(editor.getBody());
1448 
1449 			var allSelection = serializeRng(allRng);//console.log(selection, "----", allSelection);
1450 			return selection === allSelection;
1451 		}
1452 
1453 		editor.onKeyDown.add(function(editor, e) {
1454 			var keyCode = e.keyCode, isCollapsed;
1455 
1456 			// Empty the editor if it's needed for example backspace at <p><b>|</b></p>
1457 			if (!e.isDefaultPrevented() && (keyCode == DELETE || keyCode == BACKSPACE)) {
1458 				isCollapsed = editor.selection.isCollapsed();
1459 
1460 				// Selection is collapsed but the editor isn't empty
1461 				if (isCollapsed && !dom.isEmpty(editor.getBody())) {
1462 					return;
1463 				}
1464 
1465 				// IE deletes all contents correctly when everything is selected
1466 				if (tinymce.isIE && !isCollapsed) {
1467 					return;
1468 				}
1469 
1470 				// Selection isn't collapsed but not all the contents is selected
1471 				if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
1472 					return;
1473 				}
1474 
1475 				// Manually empty the editor
1476 				editor.setContent('');
1477 				editor.selection.setCursorLocation(editor.getBody(), 0);
1478 				editor.nodeChanged();
1479 			}
1480 		});
1481 	};
1482 
1483 	function selectAll() {
1484 		editor.onKeyDown.add(function(editor, e) {
1485 			if (e.keyCode == 65 && VK.metaKeyPressed(e)) {
1486 				e.preventDefault();
1487 				editor.execCommand('SelectAll');
1488 			}
1489 		});
1490 	};
1491 
1492 	function inputMethodFocus() {
1493 		if (!editor.settings.content_editable) {
1494 			// Case 1 IME doesn't initialize if you focus the document
1495 			dom.bind(editor.getDoc(), 'focusin', function(e) {
1496 				selection.setRng(selection.getRng());
1497 			});
1498 
1499 			// Case 2 IME doesn't initialize if you click the documentElement it also doesn't properly fire the focusin event
1500 			dom.bind(editor.getDoc(), 'mousedown', function(e) {
1501 				if (e.target == editor.getDoc().documentElement) {
1502 					editor.getWin().focus();
1503 					selection.setRng(selection.getRng());
1504 				}
1505 			});
1506 		}
1507 	};
1508 
1509 	function removeHrOnBackspace() {
1510 		editor.onKeyDown.add(function(editor, e) {
1511 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1512 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1513 					var node = selection.getNode();
1514 					var previousSibling = node.previousSibling;
1515 
1516 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "hr") {
1517 						dom.remove(previousSibling);
1518 						tinymce.dom.Event.cancel(e);
1519 					}
1520 				}
1521 			}
1522 		})
1523 	}
1524 
1525 	function focusBody() {
1526 		// Fix for a focus bug in FF 3.x where the body element
1527 		// wouldn't get proper focus if the user clicked on the HTML element
1528 		if (!Range.prototype.getClientRects) { // Detect getClientRects got introduced in FF 4
1529 			editor.onMouseDown.add(function(editor, e) {
1530 				if (e.target.nodeName === "HTML") {
1531 					var body = editor.getBody();
1532 
1533 					// Blur the body it's focused but not correctly focused
1534 					body.blur();
1535 
1536 					// Refocus the body after a little while
1537 					setTimeout(function() {
1538 						body.focus();
1539 					}, 0);
1540 				}
1541 			});
1542 		}
1543 	};
1544 
1545 	function selectControlElements() {
1546 		editor.onClick.add(function(editor, e) {
1547 			e = e.target;
1548 
1549 			// Workaround for bug, http://bugs.webkit.org/show_bug.cgi?id=12250
1550 			// WebKit can't even do simple things like selecting an image
1551 			// Needs tobe the setBaseAndExtend or it will fail to select floated images
1552 			if (/^(IMG|HR)$/.test(e.nodeName)) {
1553 				selection.getSel().setBaseAndExtent(e, 0, e, 1);
1554 			}
1555 
1556 			if (e.nodeName == 'A' && dom.hasClass(e, 'mceItemAnchor')) {
1557 				selection.select(e);
1558 			}
1559 
1560 			editor.nodeChanged();
1561 		});
1562 	};
1563 
1564 	function removeStylesWhenDeletingAccrossBlockElements() {
1565 		function getAttributeApplyFunction() {
1566 			var template = dom.getAttribs(selection.getStart().cloneNode(false));
1567 
1568 			return function() {
1569 				var target = selection.getStart();
1570 
1571 				if (target !== editor.getBody()) {
1572 					dom.setAttrib(target, "style", null);
1573 
1574 					tinymce.each(template, function(attr) {
1575 						target.setAttributeNode(attr.cloneNode(true));
1576 					});
1577 				}
1578 			};
1579 		}
1580 
1581 		function isSelectionAcrossElements() {
1582 			return !selection.isCollapsed() && selection.getStart() != selection.getEnd();
1583 		}
1584 
1585 		function blockEvent(editor, e) {
1586 			e.preventDefault();
1587 			return false;
1588 		}
1589 
1590 		editor.onKeyPress.add(function(editor, e) {
1591 			var applyAttributes;
1592 
1593 			if ((e.keyCode == 8 || e.keyCode == 46) && isSelectionAcrossElements()) {
1594 				applyAttributes = getAttributeApplyFunction();
1595 				editor.getDoc().execCommand('delete', false, null);
1596 				applyAttributes();
1597 				e.preventDefault();
1598 				return false;
1599 			}
1600 		});
1601 
1602 		dom.bind(editor.getDoc(), 'cut', function(e) {
1603 			var applyAttributes;
1604 
1605 			if (isSelectionAcrossElements()) {
1606 				applyAttributes = getAttributeApplyFunction();
1607 				editor.onKeyUp.addToTop(blockEvent);
1608 
1609 				setTimeout(function() {
1610 					applyAttributes();
1611 					editor.onKeyUp.remove(blockEvent);
1612 				}, 0);
1613 			}
1614 		});
1615 	}
1616 
1617 	function selectionChangeNodeChanged() {
1618 		var lastRng, selectionTimer;
1619 
1620 		dom.bind(editor.getDoc(), 'selectionchange', function() {
1621 			if (selectionTimer) {
1622 				clearTimeout(selectionTimer);
1623 				selectionTimer = 0;
1624 			}
1625 
1626 			selectionTimer = window.setTimeout(function() {
1627 				var rng = selection.getRng();
1628 
1629 				// Compare the ranges to see if it was a real change or not
1630 				if (!lastRng || !tinymce.dom.RangeUtils.compareRanges(rng, lastRng)) {
1631 					editor.nodeChanged();
1632 					lastRng = rng;
1633 				}
1634 			}, 50);
1635 		});
1636 	}
1637 
1638 	function ensureBodyHasRoleApplication() {
1639 		document.body.setAttribute("role", "application");
1640 	}
1641 
1642 	function disableBackspaceIntoATable() {
1643 		editor.onKeyDown.add(function(editor, e) {
1644 			if (!e.isDefaultPrevented() && e.keyCode === BACKSPACE) {
1645 				if (selection.isCollapsed() && selection.getRng(true).startOffset === 0) {
1646 					var previousSibling = selection.getNode().previousSibling;
1647 					if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === "table") {
1648 						return tinymce.dom.Event.cancel(e);
1649 					}
1650 				}
1651 			}
1652 		})
1653 	}
1654 
1655 	function addNewLinesBeforeBrInPre() {
1656 		// IE8+ rendering mode does the right thing with BR in PRE
1657 		if (getDocumentMode() > 7) {
1658 			return;
1659 		}
1660 
1661 		 // Enable display: none in area and add a specific class that hides all BR elements in PRE to
1662 		 // avoid the caret from getting stuck at the BR elements while pressing the right arrow key
1663 		setEditorCommandState('RespectVisibilityInDesign', true);
1664 		editor.contentStyles.push('.mceHideBrInPre pre br {display: none}');
1665 		dom.addClass(editor.getBody(), 'mceHideBrInPre');
1666 
1667 		// Adds a \n before all BR elements in PRE to get them visual
1668 		editor.parser.addNodeFilter('pre', function(nodes, name) {
1669 			var i = nodes.length, brNodes, j, brElm, sibling;
1670 
1671 			while (i--) {
1672 				brNodes = nodes[i].getAll('br');
1673 				j = brNodes.length;
1674 				while (j--) {
1675 					brElm = brNodes[j];
1676 
1677 					// Add \n before BR in PRE elements on older IE:s so the new lines get rendered
1678 					sibling = brElm.prev;
1679 					if (sibling && sibling.type === 3 && sibling.value.charAt(sibling.value - 1) != '\n') {
1680 						sibling.value += '\n';
1681 					} else {
1682 						brElm.parent.insert(new tinymce.html.Node('#text', 3), brElm, true).value = '\n';
1683 					}
1684 				}
1685 			}
1686 		});
1687 
1688 		// Removes any \n before BR elements in PRE since other browsers and in contentEditable=false mode they will be visible
1689 		editor.serializer.addNodeFilter('pre', function(nodes, name) {
1690 			var i = nodes.length, brNodes, j, brElm, sibling;
1691 
1692 			while (i--) {
1693 				brNodes = nodes[i].getAll('br');
1694 				j = brNodes.length;
1695 				while (j--) {
1696 					brElm = brNodes[j];
1697 					sibling = brElm.prev;
1698 					if (sibling && sibling.type == 3) {
1699 						sibling.value = sibling.value.replace(/\r?\n$/, '');
1700 					}
1701 				}
1702 			}
1703 		});
1704 	}
1705 
1706 	function removePreSerializedStylesWhenSelectingControls() {
1707 		dom.bind(editor.getBody(), 'mouseup', function(e) {
1708 			var value, node = selection.getNode();
1709 
1710 			// Moved styles to attributes on IMG eements
1711 			if (node.nodeName == 'IMG') {
1712 				// Convert style width to width attribute
1713 				if (value = dom.getStyle(node, 'width')) {
1714 					dom.setAttrib(node, 'width', value.replace(/[^0-9%]+/g, ''));
1715 					dom.setStyle(node, 'width', '');
1716 				}
1717 
1718 				// Convert style height to height attribute
1719 				if (value = dom.getStyle(node, 'height')) {
1720 					dom.setAttrib(node, 'height', value.replace(/[^0-9%]+/g, ''));
1721 					dom.setStyle(node, 'height', '');
1722 				}
1723 			}
1724 		});
1725 	}
1726 
1727 	function keepInlineElementOnDeleteBackspace() {
1728 		editor.onKeyDown.add(function(editor, e) {
1729 			var isDelete, rng, container, offset, brElm, sibling, collapsed;
1730 
1731 			isDelete = e.keyCode == DELETE;
1732 			if (!e.isDefaultPrevented() && (isDelete || e.keyCode == BACKSPACE) && !VK.modifierPressed(e)) {
1733 				rng = selection.getRng();
1734 				container = rng.startContainer;
1735 				offset = rng.startOffset;
1736 				collapsed = rng.collapsed;
1737 
1738 				// Override delete if the start container is a text node and is at the beginning of text or
1739 				// just before/after the last character to be deleted in collapsed mode
1740 				if (container.nodeType == 3 && container.nodeValue.length > 0 && ((offset === 0 && !collapsed) || (collapsed && offset === (isDelete ? 0 : 1)))) {
1741 					nonEmptyElements = editor.schema.getNonEmptyElements();
1742 
1743 					// Prevent default logic since it's broken
1744 					e.preventDefault();
1745 
1746 					// Insert a BR before the text node this will prevent the containing element from being deleted/converted
1747 					brElm = dom.create('br', {id: '__tmp'});
1748 					container.parentNode.insertBefore(brElm, container);
1749 
1750 					// Do the browser delete
1751 					editor.getDoc().execCommand(isDelete ? 'ForwardDelete' : 'Delete', false, null);
1752 
1753 					// Check if the previous sibling is empty after deleting for example: <p><b></b>|</p>
1754 					container = selection.getRng().startContainer;
1755 					sibling = container.previousSibling;
1756 					if (sibling && sibling.nodeType == 1 && !dom.isBlock(sibling) && dom.isEmpty(sibling) && !nonEmptyElements[sibling.nodeName.toLowerCase()]) {
1757 						dom.remove(sibling);
1758 					}
1759 
1760 					// Remove the temp element we inserted
1761 					dom.remove('__tmp');
1762 				}
1763 			}
1764 		});
1765 	}
1766 
1767 	function removeBlockQuoteOnBackSpace() {
1768 		// Add block quote deletion handler
1769 		editor.onKeyDown.add(function(editor, e) {
1770 			var rng, container, offset, root, parent;
1771 
1772 			if (e.isDefaultPrevented() || e.keyCode != VK.BACKSPACE) {
1773 				return;
1774 			}
1775 
1776 			rng = selection.getRng();
1777 			container = rng.startContainer;
1778 			offset = rng.startOffset;
1779 			root = dom.getRoot();
1780 			parent = container;
1781 
1782 			if (!rng.collapsed || offset !== 0) {
1783 				return;
1784 			}
1785 
1786 			while (parent && parent.parentNode && parent.parentNode.firstChild == parent && parent.parentNode != root) {
1787 				parent = parent.parentNode;
1788 			}
1789 
1790 			// Is the cursor at the beginning of a blockquote?
1791 			if (parent.tagName === 'BLOCKQUOTE') {
1792 				// Remove the blockquote
1793 				editor.formatter.toggle('blockquote', null, parent);
1794 
1795 				// Move the caret to the beginning of container
1796 				rng.setStart(container, 0);
1797 				rng.setEnd(container, 0);
1798 				selection.setRng(rng);
1799 				selection.collapse(false);
1800 			}
1801 		});
1802 	};
1803 
1804 	function setGeckoEditingOptions() {
1805 		function setOpts() {
1806 			editor._refreshContentEditable();
1807 
1808 			setEditorCommandState("StyleWithCSS", false);
1809 			setEditorCommandState("enableInlineTableEditing", false);
1810 
1811 			if (!settings.object_resizing) {
1812 				setEditorCommandState("enableObjectResizing", false);
1813 			}
1814 		};
1815 
1816 		if (!settings.readonly) {
1817 			editor.onBeforeExecCommand.add(setOpts);
1818 			editor.onMouseDown.add(setOpts);
1819 		}
1820 	};
1821 
1822 	function addBrAfterLastLinks() {
1823 		function fixLinks(editor, o) {
1824 			tinymce.each(dom.select('a'), function(node) {
1825 				var parentNode = node.parentNode, root = dom.getRoot();
1826 
1827 				if (parentNode.lastChild === node) {
1828 					while (parentNode && !dom.isBlock(parentNode)) {
1829 						if (parentNode.parentNode.lastChild !== parentNode || parentNode === root) {
1830 							return;
1831 						}
1832 
1833 						parentNode = parentNode.parentNode;
1834 					}
1835 
1836 					dom.add(parentNode, 'br', {'data-mce-bogus' : 1});
1837 				}
1838 			});
1839 		};
1840 
1841 		editor.onExecCommand.add(function(editor, cmd) {
1842 			if (cmd === 'CreateLink') {
1843 				fixLinks(editor);
1844 			}
1845 		});
1846 
1847 		editor.onSetContent.add(selection.onSetContent.add(fixLinks));
1848 	};
1849 
1850 	function setDefaultBlockType() {
1851 		if (settings.forced_root_block) {
1852 			editor.onInit.add(function() {
1853 				setEditorCommandState('DefaultParagraphSeparator', settings.forced_root_block);
1854 			});
1855 		}
1856 	}
1857 
1858 	function removeGhostSelection() {
1859 		function repaint(sender, args) {
1860 			if (!sender || !args.initial) {
1861 				editor.execCommand('mceRepaint');
1862 			}
1863 		};
1864 
1865 		editor.onUndo.add(repaint);
1866 		editor.onRedo.add(repaint);
1867 		editor.onSetContent.add(repaint);
1868 	};
1869 
1870 	function deleteControlItemOnBackSpace() {
1871 		editor.onKeyDown.add(function(editor, e) {
1872 			var rng;
1873 
1874 			if (!e.isDefaultPrevented() && e.keyCode == BACKSPACE) {
1875 				rng = editor.getDoc().selection.createRange();
1876 				if (rng && rng.item) {
1877 					e.preventDefault();
1878 					editor.undoManager.beforeChange();
1879 					dom.remove(rng.item(0));
1880 					editor.undoManager.add();
1881 				}
1882 			}
1883 		});
1884 	};
1885 
1886 	function renderEmptyBlocksFix() {
1887 		var emptyBlocksCSS;
1888 
1889 		// IE10+
1890 		if (getDocumentMode() >= 10) {
1891 			emptyBlocksCSS = '';
1892 			tinymce.each('p div h1 h2 h3 h4 h5 h6'.split(' '), function(name, i) {
1893 				emptyBlocksCSS += (i > 0 ? ',' : '') + name + ':empty';
1894 			});
1895 
1896 			editor.contentStyles.push(emptyBlocksCSS + '{padding-right: 1px !important}');
1897 		}
1898 	};
1899 
1900 	function fakeImageResize() {
1901 		var mouseDownImg, startX, startY, startW, startH;
1902 
1903 		if (!settings.object_resizing || settings.webkit_fake_resize === false) {
1904 			return;
1905 		}
1906 
1907 		editor.contentStyles.push('.mceResizeImages img {cursor: se-resize !important}');
1908 
1909 		function resizeImage(e) {
1910 			var deltaX, deltaY, ratio, width, height;
1911 
1912 			if (mouseDownImg) {
1913 				deltaX = e.screenX - startX;
1914 				deltaY = e.screenY - startY;
1915 				ratio = Math.max((startW + deltaX) / startW, (startH + deltaY) / startH);
1916 
1917 				// Only update styles if the user draged one pixel or more
1918 				if (Math.abs(deltaX) > 1 || Math.abs(deltaY) > 1) {
1919 					// Constrain proportions
1920 					width = Math.round(startW * ratio);
1921 					height = Math.round(startH * ratio);
1922 
1923 					// Resize by using style or attribute
1924 					if (mouseDownImg.style.width) {
1925 						dom.setStyle(mouseDownImg, 'width', width);
1926 					} else {
1927 						dom.setAttrib(mouseDownImg, 'width', width);
1928 					}
1929 
1930 					// Resize by using style or attribute
1931 					if (mouseDownImg.style.height) {
1932 						dom.setStyle(mouseDownImg, 'height', height);
1933 					} else {
1934 						dom.setAttrib(mouseDownImg, 'height', height);
1935 					}
1936 
1937 					if (!dom.hasClass(editor.getBody(), 'mceResizeImages')) {
1938 						dom.addClass(editor.getBody(), 'mceResizeImages');
1939 					}
1940 				}
1941 			}
1942 		};
1943 
1944 		editor.onMouseDown.add(function(editor, e) {
1945 			var target = e.target;
1946 
1947 			if (target.nodeName == "IMG") {
1948 				mouseDownImg = target;
1949 				startX = e.screenX;
1950 				startY = e.screenY;
1951 				startW = mouseDownImg.clientWidth;
1952 				startH = mouseDownImg.clientHeight;
1953 				dom.bind(editor.getDoc(), 'mousemove', resizeImage);
1954 				e.preventDefault();
1955 			}
1956 		});
1957 
1958 		// Unbind events on node change and restore resize cursor
1959 		editor.onNodeChange.add(function() {
1960 			if (mouseDownImg) {
1961 				mouseDownImg = null;
1962 				dom.unbind(editor.getDoc(), 'mousemove', resizeImage);
1963 			}
1964 
1965 			if (selection.getNode().nodeName == "IMG") {
1966 				dom.addClass(editor.getBody(), 'mceResizeImages');
1967 			} else {
1968 				dom.removeClass(editor.getBody(), 'mceResizeImages');
1969 			}
1970 		});
1971 	};
1972 
1973 	// All browsers
1974 	disableBackspaceIntoATable();
1975 	removeBlockQuoteOnBackSpace();
1976 	emptyEditorWhenDeleting();
1977 
1978 	// WebKit
1979 	if (tinymce.isWebKit) {
1980 		keepInlineElementOnDeleteBackspace();
1981 		cleanupStylesWhenDeleting();
1982 		inputMethodFocus();
1983 		selectControlElements();
1984 		setDefaultBlockType();
1985 
1986 		// iOS
1987 		if (tinymce.isIDevice) {
1988 			selectionChangeNodeChanged();
1989 		} else {
1990 			fakeImageResize();
1991 			selectAll();
1992 		}
1993 	}
1994 
1995 	// IE
1996 	if (tinymce.isIE) {
1997 		removeHrOnBackspace();
1998 		ensureBodyHasRoleApplication();
1999 		addNewLinesBeforeBrInPre();
2000 		removePreSerializedStylesWhenSelectingControls();
2001 		deleteControlItemOnBackSpace();
2002 		renderEmptyBlocksFix();
2003 	}
2004 
2005 	// Gecko
2006 	if (tinymce.isGecko) {
2007 		removeHrOnBackspace();
2008 		focusBody();
2009 		removeStylesWhenDeletingAccrossBlockElements();
2010 		setGeckoEditingOptions();
2011 		addBrAfterLastLinks();
2012 		removeGhostSelection();
2013 	}
2014 };
2015 (function(tinymce) {
2016 	var namedEntities, baseEntities, reverseEntities,
2017 		attrsCharsRegExp = /[&<>\"\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2018 		textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g,
2019 		rawCharsRegExp = /[<>&\"\']/g,
2020 		entityRegExp = /&(#x|#)?([\w]+);/g,
2021 		asciiMap = {
2022 				128 : "\u20AC", 130 : "\u201A", 131 : "\u0192", 132 : "\u201E", 133 : "\u2026", 134 : "\u2020",
2023 				135 : "\u2021", 136 : "\u02C6", 137 : "\u2030", 138 : "\u0160", 139 : "\u2039", 140 : "\u0152",
2024 				142 : "\u017D", 145 : "\u2018", 146 : "\u2019", 147 : "\u201C", 148 : "\u201D", 149 : "\u2022",
2025 				150 : "\u2013", 151 : "\u2014", 152 : "\u02DC", 153 : "\u2122", 154 : "\u0161", 155 : "\u203A",
2026 				156 : "\u0153", 158 : "\u017E", 159 : "\u0178"
2027 		};
2028 
2029 	// Raw entities
2030 	baseEntities = {
2031 		'\"' : '"', // Needs to be escaped since the YUI compressor would otherwise break the code
2032 		"'" : ''',
2033 		'<' : '<',
2034 		'>' : '>',
2035 		'&' : '&'
2036 	};
2037 
2038 	// Reverse lookup table for raw entities
2039 	reverseEntities = {
2040 		'<' : '<',
2041 		'>' : '>',
2042 		'&' : '&',
2043 		'"' : '"',
2044 		''' : "'"
2045 	};
2046 
2047 	// Decodes text by using the browser
2048 	function nativeDecode(text) {
2049 		var elm;
2050 
2051 		elm = document.createElement("div");
2052 		elm.innerHTML = text;
2053 
2054 		return elm.textContent || elm.innerText || text;
2055 	};
2056 
2057 	// Build a two way lookup table for the entities
2058 	function buildEntitiesLookup(items, radix) {
2059 		var i, chr, entity, lookup = {};
2060 
2061 		if (items) {
2062 			items = items.split(',');
2063 			radix = radix || 10;
2064 
2065 			// Build entities lookup table
2066 			for (i = 0; i < items.length; i += 2) {
2067 				chr = String.fromCharCode(parseInt(items[i], radix));
2068 
2069 				// Only add non base entities
2070 				if (!baseEntities[chr]) {
2071 					entity = '&' + items[i + 1] + ';';
2072 					lookup[chr] = entity;
2073 					lookup[entity] = chr;
2074 				}
2075 			}
2076 
2077 			return lookup;
2078 		}
2079 	};
2080 
2081 	// Unpack entities lookup where the numbers are in radix 32 to reduce the size
2082 	namedEntities = buildEntitiesLookup(
2083 		'50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' +
2084 		'5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' +
2085 		'5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' +
2086 		'5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' +
2087 		'68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' +
2088 		'6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' +
2089 		'6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' +
2090 		'75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' +
2091 		'7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' +
2092 		'7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' +
2093 		'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' +
2094 		'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' +
2095 		't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' +
2096 		'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' +
2097 		'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' +
2098 		'81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' +
2099 		'8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' +
2100 		'8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' +
2101 		'8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' +
2102 		'8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' +
2103 		'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' +
2104 		'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' +
2105 		'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' +
2106 		'80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' +
2107 		'811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
2108 
2109 	tinymce.html = tinymce.html || {};
2110 
2111 	tinymce.html.Entities = {
2112 		encodeRaw : function(text, attr) {
2113 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2114 				return baseEntities[chr] || chr;
2115 			});
2116 		},
2117 
2118 		encodeAllRaw : function(text) {
2119 			return ('' + text).replace(rawCharsRegExp, function(chr) {
2120 				return baseEntities[chr] || chr;
2121 			});
2122 		},
2123 
2124 		encodeNumeric : function(text, attr) {
2125 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2126 				// Multi byte sequence convert it to a single entity
2127 				if (chr.length > 1)
2128 					return '&#' + (((chr.charCodeAt(0) - 0xD800) * 0x400) + (chr.charCodeAt(1) - 0xDC00) + 0x10000) + ';';
2129 
2130 				return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
2131 			});
2132 		},
2133 
2134 		encodeNamed : function(text, attr, entities) {
2135 			entities = entities || namedEntities;
2136 
2137 			return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2138 				return baseEntities[chr] || entities[chr] || chr;
2139 			});
2140 		},
2141 
2142 		getEncodeFunc : function(name, entities) {
2143 			var Entities = tinymce.html.Entities;
2144 
2145 			entities = buildEntitiesLookup(entities) || namedEntities;
2146 
2147 			function encodeNamedAndNumeric(text, attr) {
2148 				return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, function(chr) {
2149 					return baseEntities[chr] || entities[chr] || '&#' + chr.charCodeAt(0) + ';' || chr;
2150 				});
2151 			};
2152 
2153 			function encodeCustomNamed(text, attr) {
2154 				return Entities.encodeNamed(text, attr, entities);
2155 			};
2156 
2157 			// Replace + with , to be compatible with previous TinyMCE versions
2158 			name = tinymce.makeMap(name.replace(/\+/g, ','));
2159 
2160 			// Named and numeric encoder
2161 			if (name.named && name.numeric)
2162 				return encodeNamedAndNumeric;
2163 
2164 			// Named encoder
2165 			if (name.named) {
2166 				// Custom names
2167 				if (entities)
2168 					return encodeCustomNamed;
2169 
2170 				return Entities.encodeNamed;
2171 			}
2172 
2173 			// Numeric
2174 			if (name.numeric)
2175 				return Entities.encodeNumeric;
2176 
2177 			// Raw encoder
2178 			return Entities.encodeRaw;
2179 		},
2180 
2181 		decode : function(text) {
2182 			return text.replace(entityRegExp, function(all, numeric, value) {
2183 				if (numeric) {
2184 					value = parseInt(value, numeric.length === 2 ? 16 : 10);
2185 
2186 					// Support upper UTF
2187 					if (value > 0xFFFF) {
2188 						value -= 0x10000;
2189 
2190 						return String.fromCharCode(0xD800 + (value >> 10), 0xDC00 + (value & 0x3FF));
2191 					} else
2192 						return asciiMap[value] || String.fromCharCode(value);
2193 				}
2194 
2195 				return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
2196 			});
2197 		}
2198 	};
2199 })(tinymce);
2200 
2201 tinymce.html.Styles = function(settings, schema) {
2202 	var rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi,
2203 		urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi,
2204 		styleRegExp = /\s*([^:]+):\s*([^;]+);?/g,
2205 		trimRightRegExp = /\s+$/,
2206 		urlColorRegExp = /rgb/,
2207 		undef, i, encodingLookup = {}, encodingItems;
2208 
2209 	settings = settings || {};
2210 
2211 	encodingItems = '\\" \\\' \\; \\: ; : \uFEFF'.split(' ');
2212 	for (i = 0; i < encodingItems.length; i++) {
2213 		encodingLookup[encodingItems[i]] = '\uFEFF' + i;
2214 		encodingLookup['\uFEFF' + i] = encodingItems[i];
2215 	}
2216 
2217 	function toHex(match, r, g, b) {
2218 		function hex(val) {
2219 			val = parseInt(val).toString(16);
2220 
2221 			return val.length > 1 ? val : '0' + val; // 0 -> 00
2222 		};
2223 
2224 		return '#' + hex(r) + hex(g) + hex(b);
2225 	};
2226 
2227 	return {
2228 		toHex : function(color) {
2229 			return color.replace(rgbRegExp, toHex);
2230 		},
2231 
2232 		parse : function(css) {
2233 			var styles = {}, matches, name, value, isEncoded, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope || this;
2234 
2235 			function compress(prefix, suffix) {
2236 				var top, right, bottom, left;
2237 
2238 				// Get values and check it it needs compressing
2239 				top = styles[prefix + '-top' + suffix];
2240 				if (!top)
2241 					return;
2242 
2243 				right = styles[prefix + '-right' + suffix];
2244 				if (top != right)
2245 					return;
2246 
2247 				bottom = styles[prefix + '-bottom' + suffix];
2248 				if (right != bottom)
2249 					return;
2250 
2251 				left = styles[prefix + '-left' + suffix];
2252 				if (bottom != left)
2253 					return;
2254 
2255 				// Compress
2256 				styles[prefix + suffix] = left;
2257 				delete styles[prefix + '-top' + suffix];
2258 				delete styles[prefix + '-right' + suffix];
2259 				delete styles[prefix + '-bottom' + suffix];
2260 				delete styles[prefix + '-left' + suffix];
2261 			};
2262 
2263 			function canCompress(key) {
2264 				var value = styles[key], i;
2265 
2266 				if (!value || value.indexOf(' ') < 0)
2267 					return;
2268 
2269 				value = value.split(' ');
2270 				i = value.length;
2271 				while (i--) {
2272 					if (value[i] !== value[0])
2273 						return false;
2274 				}
2275 
2276 				styles[key] = value[0];
2277 
2278 				return true;
2279 			};
2280 
2281 			function compress2(target, a, b, c) {
2282 				if (!canCompress(a))
2283 					return;
2284 
2285 				if (!canCompress(b))
2286 					return;
2287 
2288 				if (!canCompress(c))
2289 					return;
2290 
2291 				// Compress
2292 				styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
2293 				delete styles[a];
2294 				delete styles[b];
2295 				delete styles[c];
2296 			};
2297 
2298 			// Encodes the specified string by replacing all \" \' ; : with _<num>
2299 			function encode(str) {
2300 				isEncoded = true;
2301 
2302 				return encodingLookup[str];
2303 			};
2304 
2305 			// Decodes the specified string by replacing all _<num> with it's original value \" \' etc
2306 			// It will also decode the \" \' if keep_slashes is set to fale or omitted
2307 			function decode(str, keep_slashes) {
2308 				if (isEncoded) {
2309 					str = str.replace(/\uFEFF[0-9]/g, function(str) {
2310 						return encodingLookup[str];
2311 					});
2312 				}
2313 
2314 				if (!keep_slashes)
2315 					str = str.replace(/\\([\'\";:])/g, "$1");
2316 
2317 				return str;
2318 			};
2319 
2320 			function processUrl(match, url, url2, url3, str, str2) {
2321 				str = str || str2;
2322 
2323 				if (str) {
2324 					str = decode(str);
2325 
2326 					// Force strings into single quote format
2327 					return "'" + str.replace(/\'/g, "\\'") + "'";
2328 				}
2329 
2330 				url = decode(url || url2 || url3);
2331 
2332 				// Convert the URL to relative/absolute depending on config
2333 				if (urlConverter)
2334 					url = urlConverter.call(urlConverterScope, url, 'style');
2335 
2336 				// Output new URL format
2337 				return "url('" + url.replace(/\'/g, "\\'") + "')";
2338 			};
2339 
2340 			if (css) {
2341 				// Encode \" \' % and ; and : inside strings so they don't interfere with the style parsing
2342 				css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, function(str) {
2343 					return str.replace(/[;:]/g, encode);
2344 				});
2345 
2346 				// Parse styles
2347 				while (matches = styleRegExp.exec(css)) {
2348 					name = matches[1].replace(trimRightRegExp, '').toLowerCase();
2349 					value = matches[2].replace(trimRightRegExp, '');
2350 
2351 					if (name && value.length > 0) {
2352 						// Opera will produce 700 instead of bold in their style values
2353 						if (name === 'font-weight' && value === '700')
2354 							value = 'bold';
2355 						else if (name === 'color' || name === 'background-color') // Lowercase colors like RED
2356 							value = value.toLowerCase();		
2357 
2358 						// Convert RGB colors to HEX
2359 						value = value.replace(rgbRegExp, toHex);
2360 
2361 						// Convert URLs and force them into url('value') format
2362 						value = value.replace(urlOrStrRegExp, processUrl);
2363 						styles[name] = isEncoded ? decode(value, true) : value;
2364 					}
2365 
2366 					styleRegExp.lastIndex = matches.index + matches[0].length;
2367 				}
2368 
2369 				// Compress the styles to reduce it's size for example IE will expand styles
2370 				compress("border", "");
2371 				compress("border", "-width");
2372 				compress("border", "-color");
2373 				compress("border", "-style");
2374 				compress("padding", "");
2375 				compress("margin", "");
2376 				compress2('border', 'border-width', 'border-style', 'border-color');
2377 
2378 				// Remove pointless border, IE produces these
2379 				if (styles.border === 'medium none')
2380 					delete styles.border;
2381 			}
2382 
2383 			return styles;
2384 		},
2385 
2386 		serialize : function(styles, element_name) {
2387 			var css = '', name, value;
2388 
2389 			function serializeStyles(name) {
2390 				var styleList, i, l, value;
2391 
2392 				styleList = schema.styles[name];
2393 				if (styleList) {
2394 					for (i = 0, l = styleList.length; i < l; i++) {
2395 						name = styleList[i];
2396 						value = styles[name];
2397 
2398 						if (value !== undef && value.length > 0)
2399 							css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2400 					}
2401 				}
2402 			};
2403 
2404 			// Serialize styles according to schema
2405 			if (element_name && schema && schema.styles) {
2406 				// Serialize global styles and element specific styles
2407 				serializeStyles('*');
2408 				serializeStyles(element_name);
2409 			} else {
2410 				// Output the styles in the order they are inside the object
2411 				for (name in styles) {
2412 					value = styles[name];
2413 
2414 					if (value !== undef && value.length > 0)
2415 						css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
2416 				}
2417 			}
2418 
2419 			return css;
2420 		}
2421 	};
2422 };
2423 
2424 (function(tinymce) {
2425 	var mapCache = {}, makeMap = tinymce.makeMap, each = tinymce.each;
2426 
2427 	function split(str, delim) {
2428 		return str.split(delim || ',');
2429 	};
2430 
2431 	function unpack(lookup, data) {
2432 		var key, elements = {};
2433 
2434 		function replace(value) {
2435 			return value.replace(/[A-Z]+/g, function(key) {
2436 				return replace(lookup[key]);
2437 			});
2438 		};
2439 
2440 		// Unpack lookup
2441 		for (key in lookup) {
2442 			if (lookup.hasOwnProperty(key))
2443 				lookup[key] = replace(lookup[key]);
2444 		}
2445 
2446 		// Unpack and parse data into object map
2447 		replace(data).replace(/#/g, '#text').replace(/(\w+)\[([^\]]+)\]\[([^\]]*)\]/g, function(str, name, attributes, children) {
2448 			attributes = split(attributes, '|');
2449 
2450 			elements[name] = {
2451 				attributes : makeMap(attributes),
2452 				attributesOrder : attributes,
2453 				children : makeMap(children, '|', {'#comment' : {}})
2454 			}
2455 		});
2456 
2457 		return elements;
2458 	};
2459 
2460 	function getHTML5() {
2461 		var html5 = mapCache.html5;
2462 
2463 		if (!html5) {
2464 			html5 = mapCache.html5 = unpack({
2465 					A : 'id|accesskey|class|dir|draggable|item|hidden|itemprop|role|spellcheck|style|subject|title|onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2466 					B : '#|a|abbr|area|audio|b|bdo|br|button|canvas|cite|code|command|datalist|del|dfn|em|embed|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|meta|' +
2467 						'meter|noscript|object|output|progress|q|ruby|samp|script|select|small|span|strong|sub|sup|svg|textarea|time|var|video|wbr',
2468 					C : '#|a|abbr|area|address|article|aside|audio|b|bdo|blockquote|br|button|canvas|cite|code|command|datalist|del|details|dfn|dialog|div|dl|em|embed|fieldset|' +
2469 						'figure|footer|form|h1|h2|h3|h4|h5|h6|header|hgroup|hr|i|iframe|img|input|ins|kbd|keygen|label|link|map|mark|menu|meta|meter|nav|noscript|ol|object|output|' +
2470 						'p|pre|progress|q|ruby|samp|script|section|select|small|span|strong|style|sub|sup|svg|table|textarea|time|ul|var|video'
2471 				}, 'html[A|manifest][body|head]' +
2472 					'head[A][base|command|link|meta|noscript|script|style|title]' +
2473 					'title[A][#]' +
2474 					'base[A|href|target][]' +
2475 					'link[A|href|rel|media|type|sizes][]' +
2476 					'meta[A|http-equiv|name|content|charset][]' +
2477 					'style[A|type|media|scoped][#]' +
2478 					'script[A|charset|type|src|defer|async][#]' +
2479 					'noscript[A][C]' +
2480 					'body[A][C]' +
2481 					'section[A][C]' +
2482 					'nav[A][C]' +
2483 					'article[A][C]' +
2484 					'aside[A][C]' +
2485 					'h1[A][B]' +
2486 					'h2[A][B]' +
2487 					'h3[A][B]' +
2488 					'h4[A][B]' +
2489 					'h5[A][B]' +
2490 					'h6[A][B]' +
2491 					'hgroup[A][h1|h2|h3|h4|h5|h6]' +
2492 					'header[A][C]' +
2493 					'footer[A][C]' +
2494 					'address[A][C]' +
2495 					'p[A][B]' +
2496 					'br[A][]' +
2497 					'pre[A][B]' +
2498 					'dialog[A][dd|dt]' +
2499 					'blockquote[A|cite][C]' +
2500 					'ol[A|start|reversed][li]' +
2501 					'ul[A][li]' +
2502 					'li[A|value][C]' +
2503 					'dl[A][dd|dt]' +
2504 					'dt[A][B]' +
2505 					'dd[A][C]' +
2506 					'a[A|href|target|ping|rel|media|type][B]' +
2507 					'em[A][B]' +
2508 					'strong[A][B]' +
2509 					'small[A][B]' +
2510 					'cite[A][B]' +
2511 					'q[A|cite][B]' +
2512 					'dfn[A][B]' +
2513 					'abbr[A][B]' +
2514 					'code[A][B]' +
2515 					'var[A][B]' +
2516 					'samp[A][B]' +
2517 					'kbd[A][B]' +
2518 					'sub[A][B]' +
2519 					'sup[A][B]' +
2520 					'i[A][B]' +
2521 					'b[A][B]' +
2522 					'mark[A][B]' +
2523 					'progress[A|value|max][B]' +
2524 					'meter[A|value|min|max|low|high|optimum][B]' +
2525 					'time[A|datetime][B]' +
2526 					'ruby[A][B|rt|rp]' +
2527 					'rt[A][B]' +
2528 					'rp[A][B]' +
2529 					'bdo[A][B]' +
2530 					'span[A][B]' +
2531 					'ins[A|cite|datetime][B]' +
2532 					'del[A|cite|datetime][B]' +
2533 					'figure[A][C|legend|figcaption]' +
2534 					'figcaption[A][C]' +
2535 					'img[A|alt|src|height|width|usemap|ismap][]' +
2536 					'iframe[A|name|src|height|width|sandbox|seamless][]' +
2537 					'embed[A|src|height|width|type][]' +
2538 					'object[A|data|type|height|width|usemap|name|form|classid][param]' +
2539 					'param[A|name|value][]' +
2540 					'details[A|open][C|legend]' +
2541 					'command[A|type|label|icon|disabled|checked|radiogroup][]' +
2542 					'menu[A|type|label][C|li]' +
2543 					'legend[A][C|B]' +
2544 					'div[A][C]' +
2545 					'source[A|src|type|media][]' +
2546 					'audio[A|src|autobuffer|autoplay|loop|controls][source]' +
2547 					'video[A|src|autobuffer|autoplay|loop|controls|width|height|poster][source]' +
2548 					'hr[A][]' +
2549 					'form[A|accept-charset|action|autocomplete|enctype|method|name|novalidate|target][C]' +
2550 					'fieldset[A|disabled|form|name][C|legend]' +
2551 					'label[A|form|for][B]' +
2552 					'input[A|type|accept|alt|autocomplete|checked|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|height|list|max|maxlength|min|' +
2553 						'multiple|pattern|placeholder|readonly|required|size|src|step|width|files|value|name][]' +
2554 					'button[A|autofocus|disabled|form|formaction|formenctype|formmethod|formnovalidate|formtarget|name|value|type][B]' +
2555 					'select[A|autofocus|disabled|form|multiple|name|size][option|optgroup]' +
2556 					'datalist[A][B|option]' +
2557 					'optgroup[A|disabled|label][option]' +
2558 					'option[A|disabled|selected|label|value][]' +
2559 					'textarea[A|autofocus|disabled|form|maxlength|name|placeholder|readonly|required|rows|cols|wrap][]' +
2560 					'keygen[A|autofocus|challenge|disabled|form|keytype|name][]' +
2561 					'output[A|for|form|name][B]' +
2562 					'canvas[A|width|height][]' +
2563 					'map[A|name][B|C]' +
2564 					'area[A|shape|coords|href|alt|target|media|rel|ping|type][]' +
2565 					'mathml[A][]' +
2566 					'svg[A][]' +
2567 					'table[A|border][caption|colgroup|thead|tfoot|tbody|tr]' +
2568 					'caption[A][C]' +
2569 					'colgroup[A|span][col]' +
2570 					'col[A|span][]' +
2571 					'thead[A][tr]' +
2572 					'tfoot[A][tr]' +
2573 					'tbody[A][tr]' +
2574 					'tr[A][th|td]' +
2575 					'th[A|headers|rowspan|colspan|scope][B]' +
2576 					'td[A|headers|rowspan|colspan][C]' +
2577 					'wbr[A][]'
2578 			);
2579 		}
2580 
2581 		return html5;
2582 	};
2583 
2584 	function getHTML4() {
2585 		var html4 = mapCache.html4;
2586 
2587 		if (!html4) {
2588 			// This is the XHTML 1.0 transitional elements with it's attributes and children packed to reduce it's size
2589 			html4 = mapCache.html4 = unpack({
2590 				Z : 'H|K|N|O|P',
2591 				Y : 'X|form|R|Q',
2592 				ZG : 'E|span|width|align|char|charoff|valign',
2593 				X : 'p|T|div|U|W|isindex|fieldset|table',
2594 				ZF : 'E|align|char|charoff|valign',
2595 				W : 'pre|hr|blockquote|address|center|noframes',
2596 				ZE : 'abbr|axis|headers|scope|rowspan|colspan|align|char|charoff|valign|nowrap|bgcolor|width|height',
2597 				ZD : '[E][S]',
2598 				U : 'ul|ol|dl|menu|dir',
2599 				ZC : 'p|Y|div|U|W|table|br|span|bdo|object|applet|img|map|K|N|Q',
2600 				T : 'h1|h2|h3|h4|h5|h6',
2601 				ZB : 'X|S|Q',
2602 				S : 'R|P',
2603 				ZA : 'a|G|J|M|O|P',
2604 				R : 'a|H|K|N|O',
2605 				Q : 'noscript|P',
2606 				P : 'ins|del|script',
2607 				O : 'input|select|textarea|label|button',
2608 				N : 'M|L',
2609 				M : 'em|strong|dfn|code|q|samp|kbd|var|cite|abbr|acronym',
2610 				L : 'sub|sup',
2611 				K : 'J|I',
2612 				J : 'tt|i|b|u|s|strike',
2613 				I : 'big|small|font|basefont',
2614 				H : 'G|F',
2615 				G : 'br|span|bdo',
2616 				F : 'object|applet|img|map|iframe',
2617 				E : 'A|B|C',
2618 				D : 'accesskey|tabindex|onfocus|onblur',
2619 				C : 'onclick|ondblclick|onmousedown|onmouseup|onmouseover|onmousemove|onmouseout|onkeypress|onkeydown|onkeyup',
2620 				B : 'lang|xml:lang|dir',
2621 				A : 'id|class|style|title'
2622 			}, 'script[id|charset|type|language|src|defer|xml:space][]' + 
2623 				'style[B|id|type|media|title|xml:space][]' + 
2624 				'object[E|declare|classid|codebase|data|type|codetype|archive|standby|width|height|usemap|name|tabindex|align|border|hspace|vspace][#|param|Y]' + 
2625 				'param[id|name|value|valuetype|type][]' + 
2626 				'p[E|align][#|S]' + 
2627 				'a[E|D|charset|type|name|href|hreflang|rel|rev|shape|coords|target][#|Z]' + 
2628 				'br[A|clear][]' + 
2629 				'span[E][#|S]' + 
2630 				'bdo[A|C|B][#|S]' + 
2631 				'applet[A|codebase|archive|code|object|alt|name|width|height|align|hspace|vspace][#|param|Y]' + 
2632 				'h1[E|align][#|S]' + 
2633 				'img[E|src|alt|name|longdesc|width|height|usemap|ismap|align|border|hspace|vspace][]' + 
2634 				'map[B|C|A|name][X|form|Q|area]' + 
2635 				'h2[E|align][#|S]' + 
2636 				'iframe[A|longdesc|name|src|frameborder|marginwidth|marginheight|scrolling|align|width|height][#|Y]' + 
2637 				'h3[E|align][#|S]' + 
2638 				'tt[E][#|S]' + 
2639 				'i[E][#|S]' + 
2640 				'b[E][#|S]' + 
2641 				'u[E][#|S]' + 
2642 				's[E][#|S]' + 
2643 				'strike[E][#|S]' + 
2644 				'big[E][#|S]' + 
2645 				'small[E][#|S]' + 
2646 				'font[A|B|size|color|face][#|S]' + 
2647 				'basefont[id|size|color|face][]' + 
2648 				'em[E][#|S]' + 
2649 				'strong[E][#|S]' + 
2650 				'dfn[E][#|S]' + 
2651 				'code[E][#|S]' + 
2652 				'q[E|cite][#|S]' + 
2653 				'samp[E][#|S]' + 
2654 				'kbd[E][#|S]' + 
2655 				'var[E][#|S]' + 
2656 				'cite[E][#|S]' + 
2657 				'abbr[E][#|S]' + 
2658 				'acronym[E][#|S]' + 
2659 				'sub[E][#|S]' + 
2660 				'sup[E][#|S]' + 
2661 				'input[E|D|type|name|value|checked|disabled|readonly|size|maxlength|src|alt|usemap|onselect|onchange|accept|align][]' + 
2662 				'select[E|name|size|multiple|disabled|tabindex|onfocus|onblur|onchange][optgroup|option]' + 
2663 				'optgroup[E|disabled|label][option]' + 
2664 				'option[E|selected|disabled|label|value][]' + 
2665 				'textarea[E|D|name|rows|cols|disabled|readonly|onselect|onchange][]' + 
2666 				'label[E|for|accesskey|onfocus|onblur][#|S]' + 
2667 				'button[E|D|name|value|type|disabled][#|p|T|div|U|W|table|G|object|applet|img|map|K|N|Q]' + 
2668 				'h4[E|align][#|S]' + 
2669 				'ins[E|cite|datetime][#|Y]' + 
2670 				'h5[E|align][#|S]' + 
2671 				'del[E|cite|datetime][#|Y]' + 
2672 				'h6[E|align][#|S]' + 
2673 				'div[E|align][#|Y]' + 
2674 				'ul[E|type|compact][li]' + 
2675 				'li[E|type|value][#|Y]' + 
2676 				'ol[E|type|compact|start][li]' + 
2677 				'dl[E|compact][dt|dd]' + 
2678 				'dt[E][#|S]' + 
2679 				'dd[E][#|Y]' + 
2680 				'menu[E|compact][li]' + 
2681 				'dir[E|compact][li]' + 
2682 				'pre[E|width|xml:space][#|ZA]' + 
2683 				'hr[E|align|noshade|size|width][]' + 
2684 				'blockquote[E|cite][#|Y]' + 
2685 				'address[E][#|S|p]' + 
2686 				'center[E][#|Y]' + 
2687 				'noframes[E][#|Y]' + 
2688 				'isindex[A|B|prompt][]' + 
2689 				'fieldset[E][#|legend|Y]' + 
2690 				'legend[E|accesskey|align][#|S]' + 
2691 				'table[E|summary|width|border|frame|rules|cellspacing|cellpadding|align|bgcolor][caption|col|colgroup|thead|tfoot|tbody|tr]' + 
2692 				'caption[E|align][#|S]' + 
2693 				'col[ZG][]' + 
2694 				'colgroup[ZG][col]' + 
2695 				'thead[ZF][tr]' + 
2696 				'tr[ZF|bgcolor][th|td]' + 
2697 				'th[E|ZE][#|Y]' + 
2698 				'form[E|action|method|name|enctype|onsubmit|onreset|accept|accept-charset|target][#|X|R|Q]' + 
2699 				'noscript[E][#|Y]' + 
2700 				'td[E|ZE][#|Y]' + 
2701 				'tfoot[ZF][tr]' + 
2702 				'tbody[ZF][tr]' + 
2703 				'area[E|D|shape|coords|href|nohref|alt|target][]' + 
2704 				'base[id|href|target][]' + 
2705 				'body[E|onload|onunload|background|bgcolor|text|link|vlink|alink][#|Y]'
2706 			);
2707 		}
2708 
2709 		return html4;
2710 	};
2711 
2712 	tinymce.html.Schema = function(settings) {
2713 		var self = this, elements = {}, children = {}, patternElements = [], validStyles, schemaItems;
2714 		var whiteSpaceElementsMap, selfClosingElementsMap, shortEndedElementsMap, boolAttrMap, blockElementsMap, nonEmptyElementsMap, customElementsMap = {};
2715 
2716 		// Creates an lookup table map object for the specified option or the default value
2717 		function createLookupTable(option, default_value, extend) {
2718 			var value = settings[option];
2719 
2720 			if (!value) {
2721 				// Get cached default map or make it if needed
2722 				value = mapCache[option];
2723 
2724 				if (!value) {
2725 					value = makeMap(default_value, ' ', makeMap(default_value.toUpperCase(), ' '));
2726 					value = tinymce.extend(value, extend);
2727 
2728 					mapCache[option] = value;
2729 				}
2730 			} else {
2731 				// Create custom map
2732 				value = makeMap(value, ',', makeMap(value.toUpperCase(), ' '));
2733 			}
2734 
2735 			return value;
2736 		};
2737 
2738 		settings = settings || {};
2739 		schemaItems = settings.schema == "html5" ? getHTML5() : getHTML4();
2740 
2741 		// Allow all elements and attributes if verify_html is set to false
2742 		if (settings.verify_html === false)
2743 			settings.valid_elements = '*[*]';
2744 
2745 		// Build styles list
2746 		if (settings.valid_styles) {
2747 			validStyles = {};
2748 
2749 			// Convert styles into a rule list
2750 			each(settings.valid_styles, function(value, key) {
2751 				validStyles[key] = tinymce.explode(value);
2752 			});
2753 		}
2754 
2755 		// Setup map objects
2756 		whiteSpaceElementsMap = createLookupTable('whitespace_elements', 'pre script style textarea');
2757 		selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
2758 		shortEndedElementsMap = createLookupTable('short_ended_elements', 'area base basefont br col frame hr img input isindex link meta param embed source wbr');
2759 		boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize noshade nowrap readonly selected autoplay loop controls');
2760 		nonEmptyElementsMap = createLookupTable('non_empty_elements', 'td th iframe video audio object', shortEndedElementsMap);
2761 		blockElementsMap = createLookupTable('block_elements', 'h1 h2 h3 h4 h5 h6 hr p div address pre form table tbody thead tfoot ' + 
2762 						'th tr td li ol ul caption blockquote center dl dt dd dir fieldset ' + 
2763 						'noscript menu isindex samp header footer article section hgroup aside nav figure option datalist select optgroup');
2764 
2765 		// Converts a wildcard expression string to a regexp for example *a will become /.*a/.
2766 		function patternToRegExp(str) {
2767 			return new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');
2768 		};
2769 
2770 		// Parses the specified valid_elements string and adds to the current rules
2771 		// This function is a bit hard to read since it's heavily optimized for speed
2772 		function addValidElements(valid_elements) {
2773 			var ei, el, ai, al, yl, matches, element, attr, attrData, elementName, attrName, attrType, attributes, attributesOrder,
2774 				prefix, outputName, globalAttributes, globalAttributesOrder, transElement, key, childKey, value,
2775 				elementRuleRegExp = /^([#+\-])?([^\[\/]+)(?:\/([^\[]+))?(?:\[([^\]]+)\])?$/,
2776 				attrRuleRegExp = /^([!\-])?(\w+::\w+|[^=:<]+)?(?:([=:<])(.*))?$/,
2777 				hasPatternsRegExp = /[*?+]/;
2778 
2779 			if (valid_elements) {
2780 				// Split valid elements into an array with rules
2781 				valid_elements = split(valid_elements);
2782 
2783 				if (elements['@']) {
2784 					globalAttributes = elements['@'].attributes;
2785 					globalAttributesOrder = elements['@'].attributesOrder;
2786 				}
2787 
2788 				// Loop all rules
2789 				for (ei = 0, el = valid_elements.length; ei < el; ei++) {
2790 					// Parse element rule
2791 					matches = elementRuleRegExp.exec(valid_elements[ei]);
2792 					if (matches) {
2793 						// Setup local names for matches
2794 						prefix = matches[1];
2795 						elementName = matches[2];
2796 						outputName = matches[3];
2797 						attrData = matches[4];
2798 
2799 						// Create new attributes and attributesOrder
2800 						attributes = {};
2801 						attributesOrder = [];
2802 
2803 						// Create the new element
2804 						element = {
2805 							attributes : attributes,
2806 							attributesOrder : attributesOrder
2807 						};
2808 
2809 						// Padd empty elements prefix
2810 						if (prefix === '#')
2811 							element.paddEmpty = true;
2812 
2813 						// Remove empty elements prefix
2814 						if (prefix === '-')
2815 							element.removeEmpty = true;
2816 
2817 						// Copy attributes from global rule into current rule
2818 						if (globalAttributes) {
2819 							for (key in globalAttributes)
2820 								attributes[key] = globalAttributes[key];
2821 
2822 							attributesOrder.push.apply(attributesOrder, globalAttributesOrder);
2823 						}
2824 
2825 						// Attributes defined
2826 						if (attrData) {
2827 							attrData = split(attrData, '|');
2828 							for (ai = 0, al = attrData.length; ai < al; ai++) {
2829 								matches = attrRuleRegExp.exec(attrData[ai]);
2830 								if (matches) {
2831 									attr = {};
2832 									attrType = matches[1];
2833 									attrName = matches[2].replace(/::/g, ':');
2834 									prefix = matches[3];
2835 									value = matches[4];
2836 
2837 									// Required
2838 									if (attrType === '!') {
2839 										element.attributesRequired = element.attributesRequired || [];
2840 										element.attributesRequired.push(attrName);
2841 										attr.required = true;
2842 									}
2843 
2844 									// Denied from global
2845 									if (attrType === '-') {
2846 										delete attributes[attrName];
2847 										attributesOrder.splice(tinymce.inArray(attributesOrder, attrName), 1);
2848 										continue;
2849 									}
2850 
2851 									// Default value
2852 									if (prefix) {
2853 										// Default value
2854 										if (prefix === '=') {
2855 											element.attributesDefault = element.attributesDefault || [];
2856 											element.attributesDefault.push({name: attrName, value: value});
2857 											attr.defaultValue = value;
2858 										}
2859 
2860 										// Forced value
2861 										if (prefix === ':') {
2862 											element.attributesForced = element.attributesForced || [];
2863 											element.attributesForced.push({name: attrName, value: value});
2864 											attr.forcedValue = value;
2865 										}
2866 
2867 										// Required values
2868 										if (prefix === '<')
2869 											attr.validValues = makeMap(value, '?');
2870 									}
2871 
2872 									// Check for attribute patterns
2873 									if (hasPatternsRegExp.test(attrName)) {
2874 										element.attributePatterns = element.attributePatterns || [];
2875 										attr.pattern = patternToRegExp(attrName);
2876 										element.attributePatterns.push(attr);
2877 									} else {
2878 										// Add attribute to order list if it doesn't already exist
2879 										if (!attributes[attrName])
2880 											attributesOrder.push(attrName);
2881 
2882 										attributes[attrName] = attr;
2883 									}
2884 								}
2885 							}
2886 						}
2887 
2888 						// Global rule, store away these for later usage
2889 						if (!globalAttributes && elementName == '@') {
2890 							globalAttributes = attributes;
2891 							globalAttributesOrder = attributesOrder;
2892 						}
2893 
2894 						// Handle substitute elements such as b/strong
2895 						if (outputName) {
2896 							element.outputName = elementName;
2897 							elements[outputName] = element;
2898 						}
2899 
2900 						// Add pattern or exact element
2901 						if (hasPatternsRegExp.test(elementName)) {
2902 							element.pattern = patternToRegExp(elementName);
2903 							patternElements.push(element);
2904 						} else
2905 							elements[elementName] = element;
2906 					}
2907 				}
2908 			}
2909 		};
2910 
2911 		function setValidElements(valid_elements) {
2912 			elements = {};
2913 			patternElements = [];
2914 
2915 			addValidElements(valid_elements);
2916 
2917 			each(schemaItems, function(element, name) {
2918 				children[name] = element.children;
2919 			});
2920 		};
2921 
2922 		// Adds custom non HTML elements to the schema
2923 		function addCustomElements(custom_elements) {
2924 			var customElementRegExp = /^(~)?(.+)$/;
2925 
2926 			if (custom_elements) {
2927 				each(split(custom_elements), function(rule) {
2928 					var matches = customElementRegExp.exec(rule),
2929 						inline = matches[1] === '~',
2930 						cloneName = inline ? 'span' : 'div',
2931 						name = matches[2];
2932 
2933 					children[name] = children[cloneName];
2934 					customElementsMap[name] = cloneName;
2935 
2936 					// If it's not marked as inline then add it to valid block elements
2937 					if (!inline)
2938 						blockElementsMap[name] = {};
2939 
2940 					// Add custom elements at span/div positions
2941 					each(children, function(element, child) {
2942 						if (element[cloneName])
2943 							element[name] = element[cloneName];
2944 					});
2945 				});
2946 			}
2947 		};
2948 
2949 		// Adds valid children to the schema object
2950 		function addValidChildren(valid_children) {
2951 			var childRuleRegExp = /^([+\-]?)(\w+)\[([^\]]+)\]$/;
2952 
2953 			if (valid_children) {
2954 				each(split(valid_children), function(rule) {
2955 					var matches = childRuleRegExp.exec(rule), parent, prefix;
2956 
2957 					if (matches) {
2958 						prefix = matches[1];
2959 
2960 						// Add/remove items from default
2961 						if (prefix)
2962 							parent = children[matches[2]];
2963 						else
2964 							parent = children[matches[2]] = {'#comment' : {}};
2965 
2966 						parent = children[matches[2]];
2967 
2968 						each(split(matches[3], '|'), function(child) {
2969 							if (prefix === '-')
2970 								delete parent[child];
2971 							else
2972 								parent[child] = {};
2973 						});
2974 					}
2975 				});
2976 			}
2977 		};
2978 
2979 		function getElementRule(name) {
2980 			var element = elements[name], i;
2981 
2982 			// Exact match found
2983 			if (element)
2984 				return element;
2985 
2986 			// No exact match then try the patterns
2987 			i = patternElements.length;
2988 			while (i--) {
2989 				element = patternElements[i];
2990 
2991 				if (element.pattern.test(name))
2992 					return element;
2993 			}
2994 		};
2995 
2996 		if (!settings.valid_elements) {
2997 			// No valid elements defined then clone the elements from the schema spec
2998 			each(schemaItems, function(element, name) {
2999 				elements[name] = {
3000 					attributes : element.attributes,
3001 					attributesOrder : element.attributesOrder
3002 				};
3003 
3004 				children[name] = element.children;
3005 			});
3006 
3007 			// Switch these on HTML4
3008 			if (settings.schema != "html5") {
3009 				each(split('strong/b,em/i'), function(item) {
3010 					item = split(item, '/');
3011 					elements[item[1]].outputName = item[0];
3012 				});
3013 			}
3014 
3015 			// Add default alt attribute for images
3016 			elements.img.attributesDefault = [{name: 'alt', value: ''}];
3017 
3018 			// Remove these if they are empty by default
3019 			each(split('ol,ul,sub,sup,blockquote,span,font,a,table,tbody,tr,strong,em,b,i'), function(name) {
3020 				if (elements[name]) {
3021 					elements[name].removeEmpty = true;
3022 				}
3023 			});
3024 
3025 			// Padd these by default
3026 			each(split('p,h1,h2,h3,h4,h5,h6,th,td,pre,div,address,caption'), function(name) {
3027 				elements[name].paddEmpty = true;
3028 			});
3029 		} else
3030 			setValidElements(settings.valid_elements);
3031 
3032 		addCustomElements(settings.custom_elements);
3033 		addValidChildren(settings.valid_children);
3034 		addValidElements(settings.extended_valid_elements);
3035 
3036 		// Todo: Remove this when we fix list handling to be valid
3037 		addValidChildren('+ol[ul|ol],+ul[ul|ol]');
3038 
3039 		// Delete invalid elements
3040 		if (settings.invalid_elements) {
3041 			tinymce.each(tinymce.explode(settings.invalid_elements), function(item) {
3042 				if (elements[item])
3043 					delete elements[item];
3044 			});
3045 		}
3046 
3047 		// If the user didn't allow span only allow internal spans
3048 		if (!getElementRule('span'))
3049 			addValidElements('span[!data-mce-type|*]');
3050 
3051 		self.children = children;
3052 
3053 		self.styles = validStyles;
3054 
3055 		self.getBoolAttrs = function() {
3056 			return boolAttrMap;
3057 		};
3058 
3059 		self.getBlockElements = function() {
3060 			return blockElementsMap;
3061 		};
3062 
3063 		self.getShortEndedElements = function() {
3064 			return shortEndedElementsMap;
3065 		};
3066 
3067 		self.getSelfClosingElements = function() {
3068 			return selfClosingElementsMap;
3069 		};
3070 
3071 		self.getNonEmptyElements = function() {
3072 			return nonEmptyElementsMap;
3073 		};
3074 
3075 		self.getWhiteSpaceElements = function() {
3076 			return whiteSpaceElementsMap;
3077 		};
3078 
3079 		self.isValidChild = function(name, child) {
3080 			var parent = children[name];
3081 
3082 			return !!(parent && parent[child]);
3083 		};
3084 
3085 		self.isValid = function(name, attr) {
3086 			var attrPatterns, i, rule = getElementRule(name);
3087 
3088 			// Check if it's a valid element
3089 			if (rule) {
3090 				if (attr) {
3091 					// Check if attribute name exists
3092 					if (rule.attributes[attr]) {
3093 						return true;
3094 					}
3095 
3096 					// Check if attribute matches a regexp pattern
3097 					attrPatterns = rule.attributePatterns;
3098 					if (attrPatterns) {
3099 						i = attrPatterns.length;
3100 						while (i--) {
3101 							if (attrPatterns[i].pattern.test(name)) {
3102 								return true;
3103 							}
3104 						}
3105 					}
3106 				} else {
3107 					return true;
3108 				}
3109 			}
3110 
3111 			// No match
3112 			return false;
3113 		};
3114 		
3115 		self.getElementRule = getElementRule;
3116 
3117 		self.getCustomElements = function() {
3118 			return customElementsMap;
3119 		};
3120 
3121 		self.addValidElements = addValidElements;
3122 
3123 		self.setValidElements = setValidElements;
3124 
3125 		self.addCustomElements = addCustomElements;
3126 
3127 		self.addValidChildren = addValidChildren;
3128 	};
3129 })(tinymce);
3130 
3131 (function(tinymce) {
3132 	tinymce.html.SaxParser = function(settings, schema) {
3133 		var self = this, noop = function() {};
3134 
3135 		settings = settings || {};
3136 		self.schema = schema = schema || new tinymce.html.Schema();
3137 
3138 		if (settings.fix_self_closing !== false)
3139 			settings.fix_self_closing = true;
3140 
3141 		// Add handler functions from settings and setup default handlers
3142 		tinymce.each('comment cdata text start end pi doctype'.split(' '), function(name) {
3143 			if (name)
3144 				self[name] = settings[name] || noop;
3145 		});
3146 
3147 		self.parse = function(html) {
3148 			var self = this, matches, index = 0, value, endRegExp, stack = [], attrList, i, text, name, isInternalElement, removeInternalElements,
3149 				shortEndedElements, fillAttrsMap, isShortEnded, validate, elementRule, isValidElement, attr, attribsValue, invalidPrefixRegExp,
3150 				validAttributesMap, validAttributePatterns, attributesRequired, attributesDefault, attributesForced, selfClosing,
3151 				tokenRegExp, attrRegExp, specialElements, attrValue, idCount = 0, decode = tinymce.html.Entities.decode, fixSelfClosing, isIE;
3152 
3153 			function processEndTag(name) {
3154 				var pos, i;
3155 
3156 				// Find position of parent of the same type
3157 				pos = stack.length;
3158 				while (pos--) {
3159 					if (stack[pos].name === name)
3160 						break;						
3161 				}
3162 
3163 				// Found parent
3164 				if (pos >= 0) {
3165 					// Close all the open elements
3166 					for (i = stack.length - 1; i >= pos; i--) {
3167 						name = stack[i];
3168 
3169 						if (name.valid)
3170 							self.end(name.name);
3171 					}
3172 
3173 					// Remove the open elements from the stack
3174 					stack.length = pos;
3175 				}
3176 			};
3177 
3178 			function parseAttribute(match, name, value, val2, val3) {
3179 				var attrRule, i;
3180 
3181 				name = name.toLowerCase();
3182 				value = name in fillAttrsMap ? name : decode(value || val2 || val3 || ''); // Handle boolean attribute than value attribute
3183 
3184 				// Validate name and value
3185 				if (validate && !isInternalElement && name.indexOf('data-') !== 0) {
3186 					attrRule = validAttributesMap[name];
3187 
3188 					// Find rule by pattern matching
3189 					if (!attrRule && validAttributePatterns) {
3190 						i = validAttributePatterns.length;
3191 						while (i--) {
3192 							attrRule = validAttributePatterns[i];
3193 							if (attrRule.pattern.test(name))
3194 								break;
3195 						}
3196 
3197 						// No rule matched
3198 						if (i === -1)
3199 							attrRule = null;
3200 					}
3201 
3202 					// No attribute rule found
3203 					if (!attrRule)
3204 						return;
3205 
3206 					// Validate value
3207 					if (attrRule.validValues && !(value in attrRule.validValues))
3208 						return;
3209 				}
3210 
3211 				// Add attribute to list and map
3212 				attrList.map[name] = value;
3213 				attrList.push({
3214 					name: name,
3215 					value: value
3216 				});
3217 			};
3218 
3219 			// Precompile RegExps and map objects
3220 			tokenRegExp = new RegExp('<(?:' +
3221 				'(?:!--([\\w\\W]*?)-->)|' + // Comment
3222 				'(?:!\\[CDATA\\[([\\w\\W]*?)\\]\\]>)|' + // CDATA
3223 				'(?:!DOCTYPE([\\w\\W]*?)>)|' + // DOCTYPE
3224 				'(?:\\?([^\\s\\/<>]+) ?([\\w\\W]*?)[?/]>)|' + // PI
3225 				'(?:\\/([^>]+)>)|' + // End element
3226 				'(?:([A-Za-z0-9\\-\\:]+)((?:\\s+[^"\'>]+(?:(?:"[^"]*")|(?:\'[^\']*\')|[^>]*))*|\\/|\\s+)>)' + // Start element
3227 			')', 'g');
3228 
3229 			attrRegExp = /([\w:\-]+)(?:\s*=\s*(?:(?:\"((?:\\.|[^\"])*)\")|(?:\'((?:\\.|[^\'])*)\')|([^>\s]+)))?/g;
3230 			specialElements = {
3231 				'script' : /<\/script[^>]*>/gi,
3232 				'style' : /<\/style[^>]*>/gi,
3233 				'noscript' : /<\/noscript[^>]*>/gi
3234 			};
3235 
3236 			// Setup lookup tables for empty elements and boolean attributes
3237 			shortEndedElements = schema.getShortEndedElements();
3238 			selfClosing = settings.self_closing_elements || schema.getSelfClosingElements();
3239 			fillAttrsMap = schema.getBoolAttrs();
3240 			validate = settings.validate;
3241 			removeInternalElements = settings.remove_internals;
3242 			fixSelfClosing = settings.fix_self_closing;
3243 			isIE = tinymce.isIE;
3244 			invalidPrefixRegExp = /^:/;
3245 
3246 			while (matches = tokenRegExp.exec(html)) {
3247 				// Text
3248 				if (index < matches.index)
3249 					self.text(decode(html.substr(index, matches.index - index)));
3250 
3251 				if (value = matches[6]) { // End element
3252 					value = value.toLowerCase();
3253 
3254 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3255 					if (isIE && invalidPrefixRegExp.test(value))
3256 						value = value.substr(1);
3257 
3258 					processEndTag(value);
3259 				} else if (value = matches[7]) { // Start element
3260 					value = value.toLowerCase();
3261 
3262 					// IE will add a ":" in front of elements it doesn't understand like custom elements or HTML5 elements
3263 					if (isIE && invalidPrefixRegExp.test(value))
3264 						value = value.substr(1);
3265 
3266 					isShortEnded = value in shortEndedElements;
3267 
3268 					// Is self closing tag for example an <li> after an open <li>
3269 					if (fixSelfClosing && selfClosing[value] && stack.length > 0 && stack[stack.length - 1].name === value)
3270 						processEndTag(value);
3271 
3272 					// Validate element
3273 					if (!validate || (elementRule = schema.getElementRule(value))) {
3274 						isValidElement = true;
3275 
3276 						// Grab attributes map and patters when validation is enabled
3277 						if (validate) {
3278 							validAttributesMap = elementRule.attributes;
3279 							validAttributePatterns = elementRule.attributePatterns;
3280 						}
3281 
3282 						// Parse attributes
3283 						if (attribsValue = matches[8]) {
3284 							isInternalElement = attribsValue.indexOf('data-mce-type') !== -1; // Check if the element is an internal element
3285 
3286 							// If the element has internal attributes then remove it if we are told to do so
3287 							if (isInternalElement && removeInternalElements)
3288 								isValidElement = false;
3289 
3290 							attrList = [];
3291 							attrList.map = {};
3292 
3293 							attribsValue.replace(attrRegExp, parseAttribute);
3294 						} else {
3295 							attrList = [];
3296 							attrList.map = {};
3297 						}
3298 
3299 						// Process attributes if validation is enabled
3300 						if (validate && !isInternalElement) {
3301 							attributesRequired = elementRule.attributesRequired;
3302 							attributesDefault = elementRule.attributesDefault;
3303 							attributesForced = elementRule.attributesForced;
3304 
3305 							// Handle forced attributes
3306 							if (attributesForced) {
3307 								i = attributesForced.length;
3308 								while (i--) {
3309 									attr = attributesForced[i];
3310 									name = attr.name;
3311 									attrValue = attr.value;
3312 
3313 									if (attrValue === '{$uid}')
3314 										attrValue = 'mce_' + idCount++;
3315 
3316 									attrList.map[name] = attrValue;
3317 									attrList.push({name: name, value: attrValue});
3318 								}
3319 							}
3320 
3321 							// Handle default attributes
3322 							if (attributesDefault) {
3323 								i = attributesDefault.length;
3324 								while (i--) {
3325 									attr = attributesDefault[i];
3326 									name = attr.name;
3327 
3328 									if (!(name in attrList.map)) {
3329 										attrValue = attr.value;
3330 
3331 										if (attrValue === '{$uid}')
3332 											attrValue = 'mce_' + idCount++;
3333 
3334 										attrList.map[name] = attrValue;
3335 										attrList.push({name: name, value: attrValue});
3336 									}
3337 								}
3338 							}
3339 
3340 							// Handle required attributes
3341 							if (attributesRequired) {
3342 								i = attributesRequired.length;
3343 								while (i--) {
3344 									if (attributesRequired[i] in attrList.map)
3345 										break;
3346 								}
3347 
3348 								// None of the required attributes where found
3349 								if (i === -1)
3350 									isValidElement = false;
3351 							}
3352 
3353 							// Invalidate element if it's marked as bogus
3354 							if (attrList.map['data-mce-bogus'])
3355 								isValidElement = false;
3356 						}
3357 
3358 						if (isValidElement)
3359 							self.start(value, attrList, isShortEnded);
3360 					} else
3361 						isValidElement = false;
3362 
3363 					// Treat script, noscript and style a bit different since they may include code that looks like elements
3364 					if (endRegExp = specialElements[value]) {
3365 						endRegExp.lastIndex = index = matches.index + matches[0].length;
3366 
3367 						if (matches = endRegExp.exec(html)) {
3368 							if (isValidElement)
3369 								text = html.substr(index, matches.index - index);
3370 
3371 							index = matches.index + matches[0].length;
3372 						} else {
3373 							text = html.substr(index);
3374 							index = html.length;
3375 						}
3376 
3377 						if (isValidElement && text.length > 0)
3378 							self.text(text, true);
3379 
3380 						if (isValidElement)
3381 							self.end(value);
3382 
3383 						tokenRegExp.lastIndex = index;
3384 						continue;
3385 					}
3386 
3387 					// Push value on to stack
3388 					if (!isShortEnded) {
3389 						if (!attribsValue || attribsValue.indexOf('/') != attribsValue.length - 1)
3390 							stack.push({name: value, valid: isValidElement});
3391 						else if (isValidElement)
3392 							self.end(value);
3393 					}
3394 				} else if (value = matches[1]) { // Comment
3395 					self.comment(value);
3396 				} else if (value = matches[2]) { // CDATA
3397 					self.cdata(value);
3398 				} else if (value = matches[3]) { // DOCTYPE
3399 					self.doctype(value);
3400 				} else if (value = matches[4]) { // PI
3401 					self.pi(value, matches[5]);
3402 				}
3403 
3404 				index = matches.index + matches[0].length;
3405 			}
3406 
3407 			// Text
3408 			if (index < html.length)
3409 				self.text(decode(html.substr(index)));
3410 
3411 			// Close any open elements
3412 			for (i = stack.length - 1; i >= 0; i--) {
3413 				value = stack[i];
3414 
3415 				if (value.valid)
3416 					self.end(value.name);
3417 			}
3418 		};
3419 	}
3420 })(tinymce);
3421 
3422 (function(tinymce) {
3423 	var whiteSpaceRegExp = /^[ \t\r\n]*$/, typeLookup = {
3424 		'#text' : 3,
3425 		'#comment' : 8,
3426 		'#cdata' : 4,
3427 		'#pi' : 7,
3428 		'#doctype' : 10,
3429 		'#document-fragment' : 11
3430 	};
3431 
3432 	// Walks the tree left/right
3433 	function walk(node, root_node, prev) {
3434 		var sibling, parent, startName = prev ? 'lastChild' : 'firstChild', siblingName = prev ? 'prev' : 'next';
3435 
3436 		// Walk into nodes if it has a start
3437 		if (node[startName])
3438 			return node[startName];
3439 
3440 		// Return the sibling if it has one
3441 		if (node !== root_node) {
3442 			sibling = node[siblingName];
3443 
3444 			if (sibling)
3445 				return sibling;
3446 
3447 			// Walk up the parents to look for siblings
3448 			for (parent = node.parent; parent && parent !== root_node; parent = parent.parent) {
3449 				sibling = parent[siblingName];
3450 
3451 				if (sibling)
3452 					return sibling;
3453 			}
3454 		}
3455 	};
3456 
3457 	function Node(name, type) {
3458 		this.name = name;
3459 		this.type = type;
3460 
3461 		if (type === 1) {
3462 			this.attributes = [];
3463 			this.attributes.map = {};
3464 		}
3465 	}
3466 
3467 	tinymce.extend(Node.prototype, {
3468 		replace : function(node) {
3469 			var self = this;
3470 
3471 			if (node.parent)
3472 				node.remove();
3473 
3474 			self.insert(node, self);
3475 			self.remove();
3476 
3477 			return self;
3478 		},
3479 
3480 		attr : function(name, value) {
3481 			var self = this, attrs, i, undef;
3482 
3483 			if (typeof name !== "string") {
3484 				for (i in name)
3485 					self.attr(i, name[i]);
3486 
3487 				return self;
3488 			}
3489 
3490 			if (attrs = self.attributes) {
3491 				if (value !== undef) {
3492 					// Remove attribute
3493 					if (value === null) {
3494 						if (name in attrs.map) {
3495 							delete attrs.map[name];
3496 
3497 							i = attrs.length;
3498 							while (i--) {
3499 								if (attrs[i].name === name) {
3500 									attrs = attrs.splice(i, 1);
3501 									return self;
3502 								}
3503 							}
3504 						}
3505 
3506 						return self;
3507 					}
3508 
3509 					// Set attribute
3510 					if (name in attrs.map) {
3511 						// Set attribute
3512 						i = attrs.length;
3513 						while (i--) {
3514 							if (attrs[i].name === name) {
3515 								attrs[i].value = value;
3516 								break;
3517 							}
3518 						}
3519 					} else
3520 						attrs.push({name: name, value: value});
3521 
3522 					attrs.map[name] = value;
3523 
3524 					return self;
3525 				} else {
3526 					return attrs.map[name];
3527 				}
3528 			}
3529 		},
3530 
3531 		clone : function() {
3532 			var self = this, clone = new Node(self.name, self.type), i, l, selfAttrs, selfAttr, cloneAttrs;
3533 
3534 			// Clone element attributes
3535 			if (selfAttrs = self.attributes) {
3536 				cloneAttrs = [];
3537 				cloneAttrs.map = {};
3538 
3539 				for (i = 0, l = selfAttrs.length; i < l; i++) {
3540 					selfAttr = selfAttrs[i];
3541 
3542 					// Clone everything except id
3543 					if (selfAttr.name !== 'id') {
3544 						cloneAttrs[cloneAttrs.length] = {name: selfAttr.name, value: selfAttr.value};
3545 						cloneAttrs.map[selfAttr.name] = selfAttr.value;
3546 					}
3547 				}
3548 
3549 				clone.attributes = cloneAttrs;
3550 			}
3551 
3552 			clone.value = self.value;
3553 			clone.shortEnded = self.shortEnded;
3554 
3555 			return clone;
3556 		},
3557 
3558 		wrap : function(wrapper) {
3559 			var self = this;
3560 
3561 			self.parent.insert(wrapper, self);
3562 			wrapper.append(self);
3563 
3564 			return self;
3565 		},
3566 
3567 		unwrap : function() {
3568 			var self = this, node, next;
3569 
3570 			for (node = self.firstChild; node; ) {
3571 				next = node.next;
3572 				self.insert(node, self, true);
3573 				node = next;
3574 			}
3575 
3576 			self.remove();
3577 		},
3578 
3579 		remove : function() {
3580 			var self = this, parent = self.parent, next = self.next, prev = self.prev;
3581 
3582 			if (parent) {
3583 				if (parent.firstChild === self) {
3584 					parent.firstChild = next;
3585 
3586 					if (next)
3587 						next.prev = null;
3588 				} else {
3589 					prev.next = next;
3590 				}
3591 
3592 				if (parent.lastChild === self) {
3593 					parent.lastChild = prev;
3594 
3595 					if (prev)
3596 						prev.next = null;
3597 				} else {
3598 					next.prev = prev;
3599 				}
3600 
3601 				self.parent = self.next = self.prev = null;
3602 			}
3603 
3604 			return self;
3605 		},
3606 
3607 		append : function(node) {
3608 			var self = this, last;
3609 
3610 			if (node.parent)
3611 				node.remove();
3612 
3613 			last = self.lastChild;
3614 			if (last) {
3615 				last.next = node;
3616 				node.prev = last;
3617 				self.lastChild = node;
3618 			} else
3619 				self.lastChild = self.firstChild = node;
3620 
3621 			node.parent = self;
3622 
3623 			return node;
3624 		},
3625 
3626 		insert : function(node, ref_node, before) {
3627 			var parent;
3628 
3629 			if (node.parent)
3630 				node.remove();
3631 
3632 			parent = ref_node.parent || this;
3633 
3634 			if (before) {
3635 				if (ref_node === parent.firstChild)
3636 					parent.firstChild = node;
3637 				else
3638 					ref_node.prev.next = node;
3639 
3640 				node.prev = ref_node.prev;
3641 				node.next = ref_node;
3642 				ref_node.prev = node;
3643 			} else {
3644 				if (ref_node === parent.lastChild)
3645 					parent.lastChild = node;
3646 				else
3647 					ref_node.next.prev = node;
3648 
3649 				node.next = ref_node.next;
3650 				node.prev = ref_node;
3651 				ref_node.next = node;
3652 			}
3653 
3654 			node.parent = parent;
3655 
3656 			return node;
3657 		},
3658 
3659 		getAll : function(name) {
3660 			var self = this, node, collection = [];
3661 
3662 			for (node = self.firstChild; node; node = walk(node, self)) {
3663 				if (node.name === name)
3664 					collection.push(node);
3665 			}
3666 
3667 			return collection;
3668 		},
3669 
3670 		empty : function() {
3671 			var self = this, nodes, i, node;
3672 
3673 			// Remove all children
3674 			if (self.firstChild) {
3675 				nodes = [];
3676 
3677 				// Collect the children
3678 				for (node = self.firstChild; node; node = walk(node, self))
3679 					nodes.push(node);
3680 
3681 				// Remove the children
3682 				i = nodes.length;
3683 				while (i--) {
3684 					node = nodes[i];
3685 					node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
3686 				}
3687 			}
3688 
3689 			self.firstChild = self.lastChild = null;
3690 
3691 			return self;
3692 		},
3693 
3694 		isEmpty : function(elements) {
3695 			var self = this, node = self.firstChild, i, name;
3696 
3697 			if (node) {
3698 				do {
3699 					if (node.type === 1) {
3700 						// Ignore bogus elements
3701 						if (node.attributes.map['data-mce-bogus'])
3702 							continue;
3703 
3704 						// Keep empty elements like <img />
3705 						if (elements[node.name])
3706 							return false;
3707 
3708 						// Keep elements with data attributes or name attribute like <a name="1"></a>
3709 						i = node.attributes.length;
3710 						while (i--) {
3711 							name = node.attributes[i].name;
3712 							if (name === "name" || name.indexOf('data-') === 0)
3713 								return false;
3714 						}
3715 					}
3716 
3717 					// Keep comments
3718 					if (node.type === 8)
3719 						return false;
3720 					
3721 					// Keep non whitespace text nodes
3722 					if ((node.type === 3 && !whiteSpaceRegExp.test(node.value)))
3723 						return false;
3724 				} while (node = walk(node, self));
3725 			}
3726 
3727 			return true;
3728 		},
3729 
3730 		walk : function(prev) {
3731 			return walk(this, null, prev);
3732 		}
3733 	});
3734 
3735 	tinymce.extend(Node, {
3736 		create : function(name, attrs) {
3737 			var node, attrName;
3738 
3739 			// Create node
3740 			node = new Node(name, typeLookup[name] || 1);
3741 
3742 			// Add attributes if needed
3743 			if (attrs) {
3744 				for (attrName in attrs)
3745 					node.attr(attrName, attrs[attrName]);
3746 			}
3747 
3748 			return node;
3749 		}
3750 	});
3751 
3752 	tinymce.html.Node = Node;
3753 })(tinymce);
3754 
3755 (function(tinymce) {
3756 	var Node = tinymce.html.Node;
3757 
3758 	tinymce.html.DomParser = function(settings, schema) {
3759 		var self = this, nodeFilters = {}, attributeFilters = [], matchedNodes = {}, matchedAttributes = {};
3760 
3761 		settings = settings || {};
3762 		settings.validate = "validate" in settings ? settings.validate : true;
3763 		settings.root_name = settings.root_name || 'body';
3764 		self.schema = schema = schema || new tinymce.html.Schema();
3765 
3766 		function fixInvalidChildren(nodes) {
3767 			var ni, node, parent, parents, newParent, currentNode, tempNode, childNode, i,
3768 				childClone, nonEmptyElements, nonSplitableElements, sibling, nextNode;
3769 
3770 			nonSplitableElements = tinymce.makeMap('tr,td,th,tbody,thead,tfoot,table');
3771 			nonEmptyElements = schema.getNonEmptyElements();
3772 
3773 			for (ni = 0; ni < nodes.length; ni++) {
3774 				node = nodes[ni];
3775 
3776 				// Already removed
3777 				if (!node.parent)
3778 					continue;
3779 
3780 				// Get list of all parent nodes until we find a valid parent to stick the child into
3781 				parents = [node];
3782 				for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && !nonSplitableElements[parent.name]; parent = parent.parent)
3783 					parents.push(parent);
3784 
3785 				// Found a suitable parent
3786 				if (parent && parents.length > 1) {
3787 					// Reverse the array since it makes looping easier
3788 					parents.reverse();
3789 
3790 					// Clone the related parent and insert that after the moved node
3791 					newParent = currentNode = self.filterNode(parents[0].clone());
3792 
3793 					// Start cloning and moving children on the left side of the target node
3794 					for (i = 0; i < parents.length - 1; i++) {
3795 						if (schema.isValidChild(currentNode.name, parents[i].name)) {
3796 							tempNode = self.filterNode(parents[i].clone());
3797 							currentNode.append(tempNode);
3798 						} else
3799 							tempNode = currentNode;
3800 
3801 						for (childNode = parents[i].firstChild; childNode && childNode != parents[i + 1]; ) {
3802 							nextNode = childNode.next;
3803 							tempNode.append(childNode);
3804 							childNode = nextNode;
3805 						}
3806 
3807 						currentNode = tempNode;
3808 					}
3809 
3810 					if (!newParent.isEmpty(nonEmptyElements)) {
3811 						parent.insert(newParent, parents[0], true);
3812 						parent.insert(node, newParent);
3813 					} else {
3814 						parent.insert(node, parents[0], true);
3815 					}
3816 
3817 					// Check if the element is empty by looking through it's contents and special treatment for <p><br /></p>
3818 					parent = parents[0];
3819 					if (parent.isEmpty(nonEmptyElements) || parent.firstChild === parent.lastChild && parent.firstChild.name === 'br') {
3820 						parent.empty().remove();
3821 					}
3822 				} else if (node.parent) {
3823 					// If it's an LI try to find a UL/OL for it or wrap it
3824 					if (node.name === 'li') {
3825 						sibling = node.prev;
3826 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3827 							sibling.append(node);
3828 							continue;
3829 						}
3830 
3831 						sibling = node.next;
3832 						if (sibling && (sibling.name === 'ul' || sibling.name === 'ul')) {
3833 							sibling.insert(node, sibling.firstChild, true);
3834 							continue;
3835 						}
3836 
3837 						node.wrap(self.filterNode(new Node('ul', 1)));
3838 						continue;
3839 					}
3840 
3841 					// Try wrapping the element in a DIV
3842 					if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
3843 						node.wrap(self.filterNode(new Node('div', 1)));
3844 					} else {
3845 						// We failed wrapping it, then remove or unwrap it
3846 						if (node.name === 'style' || node.name === 'script')
3847 							node.empty().remove();
3848 						else
3849 							node.unwrap();
3850 					}
3851 				}
3852 			}
3853 		};
3854 
3855 		self.filterNode = function(node) {
3856 			var i, name, list;
3857 
3858 			// Run element filters
3859 			if (name in nodeFilters) {
3860 				list = matchedNodes[name];
3861 
3862 				if (list)
3863 					list.push(node);
3864 				else
3865 					matchedNodes[name] = [node];
3866 			}
3867 
3868 			// Run attribute filters
3869 			i = attributeFilters.length;
3870 			while (i--) {
3871 				name = attributeFilters[i].name;
3872 
3873 				if (name in node.attributes.map) {
3874 					list = matchedAttributes[name];
3875 
3876 					if (list)
3877 						list.push(node);
3878 					else
3879 						matchedAttributes[name] = [node];
3880 				}
3881 			}
3882 
3883 			return node;
3884 		};
3885 
3886 		self.addNodeFilter = function(name, callback) {
3887 			tinymce.each(tinymce.explode(name), function(name) {
3888 				var list = nodeFilters[name];
3889 
3890 				if (!list)
3891 					nodeFilters[name] = list = [];
3892 
3893 				list.push(callback);
3894 			});
3895 		};
3896 
3897 		self.addAttributeFilter = function(name, callback) {
3898 			tinymce.each(tinymce.explode(name), function(name) {
3899 				var i;
3900 
3901 				for (i = 0; i < attributeFilters.length; i++) {
3902 					if (attributeFilters[i].name === name) {
3903 						attributeFilters[i].callbacks.push(callback);
3904 						return;
3905 					}
3906 				}
3907 
3908 				attributeFilters.push({name: name, callbacks: [callback]});
3909 			});
3910 		};
3911 
3912 		self.parse = function(html, args) {
3913 			var parser, rootNode, node, nodes, i, l, fi, fl, list, name, validate,
3914 				blockElements, startWhiteSpaceRegExp, invalidChildren = [], isInWhiteSpacePreservedElement,
3915 				endWhiteSpaceRegExp, allWhiteSpaceRegExp, isAllWhiteSpaceRegExp, whiteSpaceElements, children, nonEmptyElements, rootBlockName;
3916 
3917 			args = args || {};
3918 			matchedNodes = {};
3919 			matchedAttributes = {};
3920 			blockElements = tinymce.extend(tinymce.makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
3921 			nonEmptyElements = schema.getNonEmptyElements();
3922 			children = schema.children;
3923 			validate = settings.validate;
3924 			rootBlockName = "forced_root_block" in args ? args.forced_root_block : settings.forced_root_block;
3925 
3926 			whiteSpaceElements = schema.getWhiteSpaceElements();
3927 			startWhiteSpaceRegExp = /^[ \t\r\n]+/;
3928 			endWhiteSpaceRegExp = /[ \t\r\n]+$/;
3929 			allWhiteSpaceRegExp = /[ \t\r\n]+/g;
3930 			isAllWhiteSpaceRegExp = /^[ \t\r\n]+$/;
3931 
3932 			function addRootBlocks() {
3933 				var node = rootNode.firstChild, next, rootBlockNode;
3934 
3935 				while (node) {
3936 					next = node.next;
3937 
3938 					if (node.type == 3 || (node.type == 1 && node.name !== 'p' && !blockElements[node.name] && !node.attr('data-mce-type'))) {
3939 						if (!rootBlockNode) {
3940 							// Create a new root block element
3941 							rootBlockNode = createNode(rootBlockName, 1);
3942 							rootNode.insert(rootBlockNode, node);
3943 							rootBlockNode.append(node);
3944 						} else
3945 							rootBlockNode.append(node);
3946 					} else {
3947 						rootBlockNode = null;
3948 					}
3949 
3950 					node = next;
3951 				};
3952 			};
3953 
3954 			function createNode(name, type) {
3955 				var node = new Node(name, type), list;
3956 
3957 				if (name in nodeFilters) {
3958 					list = matchedNodes[name];
3959 
3960 					if (list)
3961 						list.push(node);
3962 					else
3963 						matchedNodes[name] = [node];
3964 				}
3965 
3966 				return node;
3967 			};
3968 
3969 			function removeWhitespaceBefore(node) {
3970 				var textNode, textVal, sibling;
3971 
3972 				for (textNode = node.prev; textNode && textNode.type === 3; ) {
3973 					textVal = textNode.value.replace(endWhiteSpaceRegExp, '');
3974 
3975 					if (textVal.length > 0) {
3976 						textNode.value = textVal;
3977 						textNode = textNode.prev;
3978 					} else {
3979 						sibling = textNode.prev;
3980 						textNode.remove();
3981 						textNode = sibling;
3982 					}
3983 				}
3984 			};
3985 
3986 			function cloneAndExcludeBlocks(input) {
3987 				var name, output = {};
3988 
3989 				for (name in input) {
3990 					if (name !== 'li' && name != 'p') {
3991 						output[name] = input[name];
3992 					}
3993 				}
3994 
3995 				return output;
3996 			};
3997 
3998 			parser = new tinymce.html.SaxParser({
3999 				validate : validate,
4000 
4001 				// Exclude P and LI from DOM parsing since it's treated better by the DOM parser
4002 				self_closing_elements: cloneAndExcludeBlocks(schema.getSelfClosingElements()),
4003 
4004 				cdata: function(text) {
4005 					node.append(createNode('#cdata', 4)).value = text;
4006 				},
4007 
4008 				text: function(text, raw) {
4009 					var textNode;
4010 
4011 					// Trim all redundant whitespace on non white space elements
4012 					if (!isInWhiteSpacePreservedElement) {
4013 						text = text.replace(allWhiteSpaceRegExp, ' ');
4014 
4015 						if (node.lastChild && blockElements[node.lastChild.name])
4016 							text = text.replace(startWhiteSpaceRegExp, '');
4017 					}
4018 
4019 					// Do we need to create the node
4020 					if (text.length !== 0) {
4021 						textNode = createNode('#text', 3);
4022 						textNode.raw = !!raw;
4023 						node.append(textNode).value = text;
4024 					}
4025 				},
4026 
4027 				comment: function(text) {
4028 					node.append(createNode('#comment', 8)).value = text;
4029 				},
4030 
4031 				pi: function(name, text) {
4032 					node.append(createNode(name, 7)).value = text;
4033 					removeWhitespaceBefore(node);
4034 				},
4035 
4036 				doctype: function(text) {
4037 					var newNode;
4038 		
4039 					newNode = node.append(createNode('#doctype', 10));
4040 					newNode.value = text;
4041 					removeWhitespaceBefore(node);
4042 				},
4043 
4044 				start: function(name, attrs, empty) {
4045 					var newNode, attrFiltersLen, elementRule, textNode, attrName, text, sibling, parent;
4046 
4047 					elementRule = validate ? schema.getElementRule(name) : {};
4048 					if (elementRule) {
4049 						newNode = createNode(elementRule.outputName || name, 1);
4050 						newNode.attributes = attrs;
4051 						newNode.shortEnded = empty;
4052 
4053 						node.append(newNode);
4054 
4055 						// Check if node is valid child of the parent node is the child is
4056 						// unknown we don't collect it since it's probably a custom element
4057 						parent = children[node.name];
4058 						if (parent && children[newNode.name] && !parent[newNode.name])
4059 							invalidChildren.push(newNode);
4060 
4061 						attrFiltersLen = attributeFilters.length;
4062 						while (attrFiltersLen--) {
4063 							attrName = attributeFilters[attrFiltersLen].name;
4064 
4065 							if (attrName in attrs.map) {
4066 								list = matchedAttributes[attrName];
4067 
4068 								if (list)
4069 									list.push(newNode);
4070 								else
4071 									matchedAttributes[attrName] = [newNode];
4072 							}
4073 						}
4074 
4075 						// Trim whitespace before block
4076 						if (blockElements[name])
4077 							removeWhitespaceBefore(newNode);
4078 
4079 						// Change current node if the element wasn't empty i.e not <br /> or <img />
4080 						if (!empty)
4081 							node = newNode;
4082 
4083 						// Check if we are inside a whitespace preserved element
4084 						if (!isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4085 							isInWhiteSpacePreservedElement = true;
4086 						}
4087 					}
4088 				},
4089 
4090 				end: function(name) {
4091 					var textNode, elementRule, text, sibling, tempNode;
4092 
4093 					elementRule = validate ? schema.getElementRule(name) : {};
4094 					if (elementRule) {
4095 						if (blockElements[name]) {
4096 							if (!isInWhiteSpacePreservedElement) {
4097 								// Trim whitespace of the first node in a block
4098 								textNode = node.firstChild;
4099 								if (textNode && textNode.type === 3) {
4100 									text = textNode.value.replace(startWhiteSpaceRegExp, '');
4101 
4102 									// Any characters left after trim or should we remove it
4103 									if (text.length > 0) {
4104 										textNode.value = text;
4105 										textNode = textNode.next;
4106 									} else {
4107 										sibling = textNode.next;
4108 										textNode.remove();
4109 										textNode = sibling;
4110 									}
4111 
4112 									// Remove any pure whitespace siblings
4113 									while (textNode && textNode.type === 3) {
4114 										text = textNode.value;
4115 										sibling = textNode.next;
4116 
4117 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4118 											textNode.remove();
4119 											textNode = sibling;
4120 										}
4121 
4122 										textNode = sibling;
4123 									}
4124 								}
4125 
4126 								// Trim whitespace of the last node in a block
4127 								textNode = node.lastChild;
4128 								if (textNode && textNode.type === 3) {
4129 									text = textNode.value.replace(endWhiteSpaceRegExp, '');
4130 
4131 									// Any characters left after trim or should we remove it
4132 									if (text.length > 0) {
4133 										textNode.value = text;
4134 										textNode = textNode.prev;
4135 									} else {
4136 										sibling = textNode.prev;
4137 										textNode.remove();
4138 										textNode = sibling;
4139 									}
4140 
4141 									// Remove any pure whitespace siblings
4142 									while (textNode && textNode.type === 3) {
4143 										text = textNode.value;
4144 										sibling = textNode.prev;
4145 
4146 										if (text.length === 0 || isAllWhiteSpaceRegExp.test(text)) {
4147 											textNode.remove();
4148 											textNode = sibling;
4149 										}
4150 
4151 										textNode = sibling;
4152 									}
4153 								}
4154 							}
4155 
4156 							// Trim start white space
4157 							textNode = node.prev;
4158 							if (textNode && textNode.type === 3) {
4159 								text = textNode.value.replace(startWhiteSpaceRegExp, '');
4160 
4161 								if (text.length > 0)
4162 									textNode.value = text;
4163 								else
4164 									textNode.remove();
4165 							}
4166 						}
4167 
4168 						// Check if we exited a whitespace preserved element
4169 						if (isInWhiteSpacePreservedElement && whiteSpaceElements[name]) {
4170 							isInWhiteSpacePreservedElement = false;
4171 						}
4172 
4173 						// Handle empty nodes
4174 						if (elementRule.removeEmpty || elementRule.paddEmpty) {
4175 							if (node.isEmpty(nonEmptyElements)) {
4176 								if (elementRule.paddEmpty)
4177 									node.empty().append(new Node('#text', '3')).value = '\u00a0';
4178 								else {
4179 									// Leave nodes that have a name like <a name="name">
4180 									if (!node.attributes.map.name && !node.attributes.map.id) {
4181 										tempNode = node.parent;
4182 										node.empty().remove();
4183 										node = tempNode;
4184 										return;
4185 									}
4186 								}
4187 							}
4188 						}
4189 
4190 						node = node.parent;
4191 					}
4192 				}
4193 			}, schema);
4194 
4195 			rootNode = node = new Node(args.context || settings.root_name, 11);
4196 
4197 			parser.parse(html);
4198 
4199 			// Fix invalid children or report invalid children in a contextual parsing
4200 			if (validate && invalidChildren.length) {
4201 				if (!args.context)
4202 					fixInvalidChildren(invalidChildren);
4203 				else
4204 					args.invalid = true;
4205 			}
4206 
4207 			// Wrap nodes in the root into block elements if the root is body
4208 			if (rootBlockName && rootNode.name == 'body')
4209 				addRootBlocks();
4210 
4211 			// Run filters only when the contents is valid
4212 			if (!args.invalid) {
4213 				// Run node filters
4214 				for (name in matchedNodes) {
4215 					list = nodeFilters[name];
4216 					nodes = matchedNodes[name];
4217 
4218 					// Remove already removed children
4219 					fi = nodes.length;
4220 					while (fi--) {
4221 						if (!nodes[fi].parent)
4222 							nodes.splice(fi, 1);
4223 					}
4224 
4225 					for (i = 0, l = list.length; i < l; i++)
4226 						list[i](nodes, name, args);
4227 				}
4228 
4229 				// Run attribute filters
4230 				for (i = 0, l = attributeFilters.length; i < l; i++) {
4231 					list = attributeFilters[i];
4232 
4233 					if (list.name in matchedAttributes) {
4234 						nodes = matchedAttributes[list.name];
4235 
4236 						// Remove already removed children
4237 						fi = nodes.length;
4238 						while (fi--) {
4239 							if (!nodes[fi].parent)
4240 								nodes.splice(fi, 1);
4241 						}
4242 
4243 						for (fi = 0, fl = list.callbacks.length; fi < fl; fi++)
4244 							list.callbacks[fi](nodes, list.name, args);
4245 					}
4246 				}
4247 			}
4248 
4249 			return rootNode;
4250 		};
4251 
4252 		// Remove <br> at end of block elements Gecko and WebKit injects BR elements to
4253 		// make it possible to place the caret inside empty blocks. This logic tries to remove
4254 		// these elements and keep br elements that where intended to be there intact
4255 		if (settings.remove_trailing_brs) {
4256 			self.addNodeFilter('br', function(nodes, name) {
4257 				var i, l = nodes.length, node, blockElements = tinymce.extend({}, schema.getBlockElements()),
4258 					nonEmptyElements = schema.getNonEmptyElements(), parent, lastParent, prev, prevName;
4259 
4260 				// Remove brs from body element as well
4261 				blockElements.body = 1;
4262 
4263 				// Must loop forwards since it will otherwise remove all brs in <p>a<br><br><br></p>
4264 				for (i = 0; i < l; i++) {
4265 					node = nodes[i];
4266 					parent = node.parent;
4267 
4268 					if (blockElements[node.parent.name] && node === parent.lastChild) {
4269 						// Loop all nodes to the left of the current node and check for other BR elements
4270 						// excluding bookmarks since they are invisible
4271 						prev = node.prev;
4272 						while (prev) {
4273 							prevName = prev.name;
4274 
4275 							// Ignore bookmarks
4276 							if (prevName !== "span" || prev.attr('data-mce-type') !== 'bookmark') {
4277 								// Found a non BR element
4278 								if (prevName !== "br")
4279 									break;
4280 	
4281 								// Found another br it's a <br><br> structure then don't remove anything
4282 								if (prevName === 'br') {
4283 									node = null;
4284 									break;
4285 								}
4286 							}
4287 
4288 							prev = prev.prev;
4289 						}
4290 
4291 						if (node) {
4292 							node.remove();
4293 
4294 							// Is the parent to be considered empty after we removed the BR
4295 							if (parent.isEmpty(nonEmptyElements)) {
4296 								elementRule = schema.getElementRule(parent.name);
4297 
4298 								// Remove or padd the element depending on schema rule
4299 								if (elementRule) {
4300 									if (elementRule.removeEmpty)
4301 										parent.remove();
4302 									else if (elementRule.paddEmpty)
4303 										parent.empty().append(new tinymce.html.Node('#text', 3)).value = '\u00a0';
4304 								}
4305 							}
4306 						}
4307 					} else {
4308 						// Replaces BR elements inside inline elements like <p><b><i><br></i></b></p> so they become <p><b><i> </i></b></p> 
4309 						lastParent = node;
4310 						while (parent.firstChild === lastParent && parent.lastChild === lastParent) {
4311 							lastParent = parent;
4312 
4313 							if (blockElements[parent.name]) {
4314 								break;
4315 							}
4316 
4317 							parent = parent.parent;
4318 						}
4319 
4320 						if (lastParent === parent) {
4321 							textNode = new tinymce.html.Node('#text', 3);
4322 							textNode.value = '\u00a0';
4323 							node.replace(textNode);
4324 						}
4325 					}
4326 				}
4327 			});
4328 		}
4329 
4330 		// Force anchor names closed, unless the setting "allow_html_in_named_anchor" is explicitly included.
4331 		if (!settings.allow_html_in_named_anchor) {
4332 			self.addAttributeFilter('id,name', function(nodes, name) {
4333 				var i = nodes.length, sibling, prevSibling, parent, node;
4334 
4335 				while (i--) {
4336 					node = nodes[i];
4337 					if (node.name === 'a' && node.firstChild && !node.attr('href')) {
4338 						parent = node.parent;
4339 
4340 						// Move children after current node
4341 						sibling = node.lastChild;
4342 						do {
4343 							prevSibling = sibling.prev;
4344 							parent.insert(sibling, node);
4345 							sibling = prevSibling;
4346 						} while (sibling);
4347 					}
4348 				}
4349 			});
4350 		}
4351 	}
4352 })(tinymce);
4353 
4354 tinymce.html.Writer = function(settings) {
4355 	var html = [], indent, indentBefore, indentAfter, encode, htmlOutput;
4356 
4357 	settings = settings || {};
4358 	indent = settings.indent;
4359 	indentBefore = tinymce.makeMap(settings.indent_before || '');
4360 	indentAfter = tinymce.makeMap(settings.indent_after || '');
4361 	encode = tinymce.html.Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
4362 	htmlOutput = settings.element_format == "html";
4363 
4364 	return {
4365 		start: function(name, attrs, empty) {
4366 			var i, l, attr, value;
4367 
4368 			if (indent && indentBefore[name] && html.length > 0) {
4369 				value = html[html.length - 1];
4370 
4371 				if (value.length > 0 && value !== '\n')
4372 					html.push('\n');
4373 			}
4374 
4375 			html.push('<', name);
4376 
4377 			if (attrs) {
4378 				for (i = 0, l = attrs.length; i < l; i++) {
4379 					attr = attrs[i];
4380 					html.push(' ', attr.name, '="', encode(attr.value, true), '"');
4381 				}
4382 			}
4383 
4384 			if (!empty || htmlOutput)
4385 				html[html.length] = '>';
4386 			else
4387 				html[html.length] = ' />';
4388 
4389 			if (empty && indent && indentAfter[name] && html.length > 0) {
4390 				value = html[html.length - 1];
4391 
4392 				if (value.length > 0 && value !== '\n')
4393 					html.push('\n');
4394 			}
4395 		},
4396 
4397 		end: function(name) {
4398 			var value;
4399 
4400 			/*if (indent && indentBefore[name] && html.length > 0) {
4401 				value = html[html.length - 1];
4402 
4403 				if (value.length > 0 && value !== '\n')
4404 					html.push('\n');
4405 			}*/
4406 
4407 			html.push('</', name, '>');
4408 
4409 			if (indent && indentAfter[name] && html.length > 0) {
4410 				value = html[html.length - 1];
4411 
4412 				if (value.length > 0 && value !== '\n')
4413 					html.push('\n');
4414 			}
4415 		},
4416 
4417 		text: function(text, raw) {
4418 			if (text.length > 0)
4419 				html[html.length] = raw ? text : encode(text);
4420 		},
4421 
4422 		cdata: function(text) {
4423 			html.push('<![CDATA[', text, ']]>');
4424 		},
4425 
4426 		comment: function(text) {
4427 			html.push('<!--', text, '-->');
4428 		},
4429 
4430 		pi: function(name, text) {
4431 			if (text)
4432 				html.push('<?', name, ' ', text, '?>');
4433 			else
4434 				html.push('<?', name, '?>');
4435 
4436 			if (indent)
4437 				html.push('\n');
4438 		},
4439 
4440 		doctype: function(text) {
4441 			html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
4442 		},
4443 
4444 		reset: function() {
4445 			html.length = 0;
4446 		},
4447 
4448 		getContent: function() {
4449 			return html.join('').replace(/\n$/, '');
4450 		}
4451 	};
4452 };
4453 
4454 (function(tinymce) {
4455 	tinymce.html.Serializer = function(settings, schema) {
4456 		var self = this, writer = new tinymce.html.Writer(settings);
4457 
4458 		settings = settings || {};
4459 		settings.validate = "validate" in settings ? settings.validate : true;
4460 
4461 		self.schema = schema = schema || new tinymce.html.Schema();
4462 		self.writer = writer;
4463 
4464 		self.serialize = function(node) {
4465 			var handlers, validate;
4466 
4467 			validate = settings.validate;
4468 
4469 			handlers = {
4470 				// #text
4471 				3: function(node, raw) {
4472 					writer.text(node.value, node.raw);
4473 				},
4474 
4475 				// #comment
4476 				8: function(node) {
4477 					writer.comment(node.value);
4478 				},
4479 
4480 				// Processing instruction
4481 				7: function(node) {
4482 					writer.pi(node.name, node.value);
4483 				},
4484 
4485 				// Doctype
4486 				10: function(node) {
4487 					writer.doctype(node.value);
4488 				},
4489 
4490 				// CDATA
4491 				4: function(node) {
4492 					writer.cdata(node.value);
4493 				},
4494 
4495 				// Document fragment
4496 				11: function(node) {
4497 					if ((node = node.firstChild)) {
4498 						do {
4499 							walk(node);
4500 						} while (node = node.next);
4501 					}
4502 				}
4503 			};
4504 
4505 			writer.reset();
4506 
4507 			function walk(node) {
4508 				var handler = handlers[node.type], name, isEmpty, attrs, attrName, attrValue, sortedAttrs, i, l, elementRule;
4509 
4510 				if (!handler) {
4511 					name = node.name;
4512 					isEmpty = node.shortEnded;
4513 					attrs = node.attributes;
4514 
4515 					// Sort attributes
4516 					if (validate && attrs && attrs.length > 1) {
4517 						sortedAttrs = [];
4518 						sortedAttrs.map = {};
4519 
4520 						elementRule = schema.getElementRule(node.name);
4521 						for (i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
4522 							attrName = elementRule.attributesOrder[i];
4523 
4524 							if (attrName in attrs.map) {
4525 								attrValue = attrs.map[attrName];
4526 								sortedAttrs.map[attrName] = attrValue;
4527 								sortedAttrs.push({name: attrName, value: attrValue});
4528 							}
4529 						}
4530 
4531 						for (i = 0, l = attrs.length; i < l; i++) {
4532 							attrName = attrs[i].name;
4533 
4534 							if (!(attrName in sortedAttrs.map)) {
4535 								attrValue = attrs.map[attrName];
4536 								sortedAttrs.map[attrName] = attrValue;
4537 								sortedAttrs.push({name: attrName, value: attrValue});
4538 							}
4539 						}
4540 
4541 						attrs = sortedAttrs;
4542 					}
4543 
4544 					writer.start(node.name, attrs, isEmpty);
4545 
4546 					if (!isEmpty) {
4547 						if ((node = node.firstChild)) {
4548 							do {
4549 								walk(node);
4550 							} while (node = node.next);
4551 						}
4552 
4553 						writer.end(name);
4554 					}
4555 				} else
4556 					handler(node);
4557 			}
4558 
4559 			// Serialize element and treat all non elements as fragments
4560 			if (node.type == 1 && !settings.inner)
4561 				walk(node);
4562 			else
4563 				handlers[11](node);
4564 
4565 			return writer.getContent();
4566 		};
4567 	}
4568 })(tinymce);
4569 
4570 // JSLint defined globals
4571 /*global tinymce:false, window:false */
4572 
4573 tinymce.dom = {};
4574 
4575 (function(namespace, expando) {
4576 	var w3cEventModel = !!document.addEventListener;
4577 
4578 	function addEvent(target, name, callback, capture) {
4579 		if (target.addEventListener) {
4580 			target.addEventListener(name, callback, capture || false);
4581 		} else if (target.attachEvent) {
4582 			target.attachEvent('on' + name, callback);
4583 		}
4584 	}
4585 
4586 	function removeEvent(target, name, callback, capture) {
4587 		if (target.removeEventListener) {
4588 			target.removeEventListener(name, callback, capture || false);
4589 		} else if (target.detachEvent) {
4590 			target.detachEvent('on' + name, callback);
4591 		}
4592 	}
4593 
4594 	function fix(original_event, data) {
4595 		var name, event = data || {};
4596 
4597 		// Dummy function that gets replaced on the delegation state functions
4598 		function returnFalse() {
4599 			return false;
4600 		}
4601 
4602 		// Dummy function that gets replaced on the delegation state functions
4603 		function returnTrue() {
4604 			return true;
4605 		}
4606 
4607 		// Copy all properties from the original event
4608 		for (name in original_event) {
4609 			// layerX/layerY is deprecated in Chrome and produces a warning
4610 			if (name !== "layerX" && name !== "layerY") {
4611 				event[name] = original_event[name];
4612 			}
4613 		}
4614 
4615 		// Normalize target IE uses srcElement
4616 		if (!event.target) {
4617 			event.target = event.srcElement || document;
4618 		}
4619 
4620 		// Add preventDefault method
4621 		event.preventDefault = function() {
4622 			event.isDefaultPrevented = returnTrue;
4623 
4624 			// Execute preventDefault on the original event object
4625 			if (original_event) {
4626 				if (original_event.preventDefault) {
4627 					original_event.preventDefault();
4628 				} else {
4629 					original_event.returnValue = false; // IE
4630 				}
4631 			}
4632 		};
4633 
4634 		// Add stopPropagation
4635 		event.stopPropagation = function() {
4636 			event.isPropagationStopped = returnTrue;
4637 
4638 			// Execute stopPropagation on the original event object
4639 			if (original_event) {
4640 				if (original_event.stopPropagation) {
4641 					original_event.stopPropagation();
4642 				} else {
4643 					original_event.cancelBubble = true; // IE
4644 				}
4645 			 }
4646 		};
4647 
4648 		// Add stopImmediatePropagation
4649 		event.stopImmediatePropagation = function() {
4650 			event.isImmediatePropagationStopped = returnTrue;
4651 			event.stopPropagation();
4652 		};
4653 
4654 		// Add event delegation states
4655 		if (!event.isDefaultPrevented) {
4656 			event.isDefaultPrevented = returnFalse;
4657 			event.isPropagationStopped = returnFalse;
4658 			event.isImmediatePropagationStopped = returnFalse;
4659 		}
4660 
4661 		return event;
4662 	}
4663 
4664 	function bindOnReady(win, callback, event_utils) {
4665 		var doc = win.document, event = {type: 'ready'};
4666 
4667 		// Gets called when the DOM is ready
4668 		function readyHandler() {
4669 			if (!event_utils.domLoaded) {
4670 				event_utils.domLoaded = true;
4671 				callback(event);
4672 			}
4673 		}
4674 
4675 		// Use W3C method
4676 		if (w3cEventModel) {
4677 			addEvent(win, 'DOMContentLoaded', readyHandler);
4678 		} else {
4679 			// Use IE method
4680 			addEvent(doc, "readystatechange", function() {
4681 				if (doc.readyState === "complete") {
4682 					removeEvent(doc, "readystatechange", arguments.callee);
4683 					readyHandler();
4684 				}
4685 			});
4686 
4687 			// Wait until we can scroll, when we can the DOM is initialized
4688 			if (doc.documentElement.doScroll && win === win.top) {
4689 				(function() {
4690 					try {
4691 						// If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
4692 						// http://javascript.nwbox.com/IEContentLoaded/
4693 						doc.documentElement.doScroll("left");
4694 					} catch (ex) {
4695 						setTimeout(arguments.callee, 0);
4696 						return;
4697 					}
4698 
4699 					readyHandler();
4700 				})();
4701 			}
4702 		}
4703 
4704 		// Fallback if any of the above methods should fail for some odd reason
4705 		addEvent(win, 'load', readyHandler);
4706 	}
4707 
4708 	function EventUtils(proxy) {
4709 		var self = this, events = {}, count, isFocusBlurBound, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
4710 
4711 		hasMouseEnterLeave = "onmouseenter" in document.documentElement;
4712 		hasFocusIn = "onfocusin" in document.documentElement;
4713 		mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
4714 		count = 1;
4715 
4716 		// State if the DOMContentLoaded was executed or not
4717 		self.domLoaded = false;
4718 		self.events = events;
4719 
4720 		function executeHandlers(evt, id) {
4721 			var callbackList, i, l, callback;
4722 
4723 			callbackList = events[id][evt.type];
4724 			if (callbackList) {
4725 				for (i = 0, l = callbackList.length; i < l; i++) {
4726 					callback = callbackList[i];
4727 					
4728 					// Check if callback exists might be removed if a unbind is called inside the callback
4729 					if (callback && callback.func.call(callback.scope, evt) === false) {
4730 						evt.preventDefault();
4731 					}
4732 
4733 					// Should we stop propagation to immediate listeners
4734 					if (evt.isImmediatePropagationStopped()) {
4735 						return;
4736 					}
4737 				}
4738 			}
4739 		}
4740 
4741 		self.bind = function(target, names, callback, scope) {
4742 			var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
4743 
4744 			// Native event handler function patches the event and executes the callbacks for the expando
4745 			function defaultNativeHandler(evt) {
4746 				executeHandlers(fix(evt || win.event), id);
4747 			}
4748 
4749 			// Don't bind to text nodes or comments
4750 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4751 				return;
4752 			}
4753 
4754 			// Create or get events id for the target
4755 			if (!target[expando]) {
4756 				id = count++;
4757 				target[expando] = id;
4758 				events[id] = {};
4759 			} else {
4760 				id = target[expando];
4761 
4762 				if (!events[id]) {
4763 					events[id] = {};
4764 				}
4765 			}
4766 
4767 			// Setup the specified scope or use the target as a default
4768 			scope = scope || target;
4769 
4770 			// Split names and bind each event, enables you to bind multiple events with one call
4771 			names = names.split(' ');
4772 			i = names.length;
4773 			while (i--) {
4774 				name = names[i];
4775 				nativeHandler = defaultNativeHandler;
4776 				fakeName = capture = false;
4777 
4778 				// Use ready instead of DOMContentLoaded
4779 				if (name === "DOMContentLoaded") {
4780 					name = "ready";
4781 				}
4782 
4783 				// DOM is already ready
4784 				if ((self.domLoaded || target.readyState == 'complete') && name === "ready") {
4785 					self.domLoaded = true;
4786 					callback.call(scope, fix({type: name}));
4787 					continue;
4788 				}
4789 
4790 				// Handle mouseenter/mouseleaver
4791 				if (!hasMouseEnterLeave) {
4792 					fakeName = mouseEnterLeave[name];
4793 
4794 					if (fakeName) {
4795 						nativeHandler = function(evt) {
4796 							var current, related;
4797 
4798 							current = evt.currentTarget;
4799 							related = evt.relatedTarget;
4800 
4801 							// Check if related is inside the current target if it's not then the event should be ignored since it's a mouseover/mouseout inside the element
4802 							if (related && current.contains) {
4803 								// Use contains for performance
4804 								related = current.contains(related);
4805 							} else {
4806 								while (related && related !== current) {
4807 									related = related.parentNode;
4808 								}
4809 							}
4810 
4811 							// Fire fake event
4812 							if (!related) {
4813 								evt = fix(evt || win.event);
4814 								evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
4815 								evt.target = current;
4816 								executeHandlers(evt, id);
4817 							}
4818 						};
4819 					}
4820 				}
4821 
4822 				// Fake bubbeling of focusin/focusout
4823 				if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
4824 					capture = true;
4825 					fakeName = name === "focusin" ? "focus" : "blur";
4826 					nativeHandler = function(evt) {
4827 						evt = fix(evt || win.event);
4828 						evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
4829 						executeHandlers(evt, id);
4830 					};
4831 				}
4832 
4833 				// Setup callback list and bind native event
4834 				callbackList = events[id][name];
4835 				if (!callbackList) {
4836 					events[id][name] = callbackList = [{func: callback, scope: scope}];
4837 					callbackList.fakeName = fakeName;
4838 					callbackList.capture = capture;
4839 
4840 					// Add the nativeHandler to the callback list so that we can later unbind it
4841 					callbackList.nativeHandler = nativeHandler;
4842 					if (!w3cEventModel) {
4843 						callbackList.proxyHandler = proxy(id);
4844 					}
4845 
4846 					// Check if the target has native events support
4847 					if (name === "ready") {
4848 						bindOnReady(target, nativeHandler, self);
4849 					} else {
4850 						addEvent(target, fakeName || name, w3cEventModel ? nativeHandler : callbackList.proxyHandler, capture);
4851 					}
4852 				} else {
4853 					// If it already has an native handler then just push the callback
4854 					callbackList.push({func: callback, scope: scope});
4855 				}
4856 			}
4857 
4858 			target = callbackList = 0; // Clean memory for IE
4859 
4860 			return callback;
4861 		};
4862 
4863 		self.unbind = function(target, names, callback) {
4864 			var id, callbackList, i, ci, name, eventMap;
4865 
4866 			// Don't bind to text nodes or comments
4867 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4868 				return self;
4869 			}
4870 
4871 			// Unbind event or events if the target has the expando
4872 			id = target[expando];
4873 			if (id) {
4874 				eventMap = events[id];
4875 
4876 				// Specific callback
4877 				if (names) {
4878 					names = names.split(' ');
4879 					i = names.length;
4880 					while (i--) {
4881 						name = names[i];
4882 						callbackList = eventMap[name];
4883 
4884 						// Unbind the event if it exists in the map
4885 						if (callbackList) {
4886 							// Remove specified callback
4887 							if (callback) {
4888 								ci = callbackList.length;
4889 								while (ci--) {
4890 									if (callbackList[ci].func === callback) {
4891 										callbackList.splice(ci, 1);
4892 									}
4893 								}
4894 							}
4895 
4896 							// Remove all callbacks if there isn't a specified callback or there is no callbacks left
4897 							if (!callback || callbackList.length === 0) {
4898 								delete eventMap[name];
4899 								removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4900 							}
4901 						}
4902 					}
4903 				} else {
4904 					// All events for a specific element
4905 					for (name in eventMap) {
4906 						callbackList = eventMap[name];
4907 						removeEvent(target, callbackList.fakeName || name, w3cEventModel ? callbackList.nativeHandler : callbackList.proxyHandler, callbackList.capture);
4908 					}
4909 
4910 					eventMap = {};
4911 				}
4912 
4913 				// Check if object is empty, if it isn't then we won't remove the expando map
4914 				for (name in eventMap) {
4915 					return self;
4916 				}
4917 
4918 				// Delete event object
4919 				delete events[id];
4920 
4921 				// Remove expando from target
4922 				try {
4923 					// IE will fail here since it can't delete properties from window
4924 					delete target[expando];
4925 				} catch (ex) {
4926 					// IE will set it to null
4927 					target[expando] = null;
4928 				}
4929 			}
4930 
4931 			return self;
4932 		};
4933 
4934 		self.fire = function(target, name, args) {
4935 			var id, event;
4936 
4937 			// Don't bind to text nodes or comments
4938 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4939 				return self;
4940 			}
4941 
4942 			// Build event object by patching the args
4943 			event = fix(null, args);
4944 			event.type = name;
4945 
4946 			do {
4947 				// Found an expando that means there is listeners to execute
4948 				id = target[expando];
4949 				if (id) {
4950 					executeHandlers(event, id);
4951 				}
4952 
4953 				// Walk up the DOM
4954 				target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
4955 			} while (target && !event.isPropagationStopped());
4956 
4957 			return self;
4958 		};
4959 
4960 		self.clean = function(target) {
4961 			var i, children, unbind = self.unbind;
4962 	
4963 			// Don't bind to text nodes or comments
4964 			if (!target || target.nodeType === 3 || target.nodeType === 8) {
4965 				return self;
4966 			}
4967 
4968 			// Unbind any element on the specificed target
4969 			if (target[expando]) {
4970 				unbind(target);
4971 			}
4972 
4973 			// Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
4974 			if (!target.getElementsByTagName) {
4975 				target = target.document;
4976 			}
4977 
4978 			// Remove events from each child element
4979 			if (target && target.getElementsByTagName) {
4980 				unbind(target);
4981 
4982 				children = target.getElementsByTagName('*');
4983 				i = children.length;
4984 				while (i--) {
4985 					target = children[i];
4986 
4987 					if (target[expando]) {
4988 						unbind(target);
4989 					}
4990 				}
4991 			}
4992 
4993 			return self;
4994 		};
4995 
4996 		self.callNativeHandler = function(id, evt) {
4997 			if (events) {
4998 				events[id][evt.type].nativeHandler(evt);
4999 			}
5000 		};
5001 
5002 		self.destory = function() {
5003 			events = {};
5004 		};
5005 
5006 		// Legacy function calls
5007 
5008 		self.add = function(target, events, func, scope) {
5009 			// Old API supported direct ID assignment
5010 			if (typeof(target) === "string") {
5011 				target = document.getElementById(target);
5012 			}
5013 
5014 			// Old API supported multiple targets
5015 			if (target && target instanceof Array) {
5016 				var i = target.length;
5017 
5018 				while (i--) {
5019 					self.add(target[i], events, func, scope);
5020 				}
5021 
5022 				return;
5023 			}
5024 
5025 			// Old API called ready init
5026 			if (events === "init") {
5027 				events = "ready";
5028 			}
5029 
5030 			return self.bind(target, events instanceof Array ? events.join(' ') : events, func, scope);
5031 		};
5032 
5033 		self.remove = function(target, events, func, scope) {
5034 			if (!target) {
5035 				return self;
5036 			}
5037 
5038 			// Old API supported direct ID assignment
5039 			if (typeof(target) === "string") {
5040 				target = document.getElementById(target);
5041 			}
5042 
5043 			// Old API supported multiple targets
5044 			if (target instanceof Array) {
5045 				var i = target.length;
5046 
5047 				while (i--) {
5048 					self.remove(target[i], events, func, scope);
5049 				}
5050 
5051 				return self;
5052 			}
5053 
5054 			return self.unbind(target, events instanceof Array ? events.join(' ') : events, func);
5055 		};
5056 
5057 		self.clear = function(target) {
5058 			// Old API supported direct ID assignment
5059 			if (typeof(target) === "string") {
5060 				target = document.getElementById(target);
5061 			}
5062 
5063 			return self.clean(target);
5064 		};
5065 
5066 		self.cancel = function(e) {
5067 			if (e) {
5068 				self.prevent(e);
5069 				self.stop(e);
5070 			}
5071 
5072 			return false;
5073 		};
5074 
5075 		self.prevent = function(e) {
5076 			if (!e.preventDefault) {
5077 				e = fix(e);
5078 			}
5079 
5080 			e.preventDefault();
5081 
5082 			return false;
5083 		};
5084 
5085 		self.stop = function(e) {
5086 			if (!e.stopPropagation) {
5087 				e = fix(e);
5088 			}
5089 
5090 			e.stopPropagation();
5091 
5092 			return false;
5093 		};
5094 	}
5095 
5096 	namespace.EventUtils = EventUtils;
5097 
5098 	namespace.Event = new EventUtils(function(id) {
5099 		return function(evt) {
5100 			tinymce.dom.Event.callNativeHandler(id, evt);
5101 		};
5102 	});
5103 
5104 	// Bind ready event when tinymce script is loaded
5105 	namespace.Event.bind(window, 'ready', function() {});
5106 
5107 	namespace = 0;
5108 })(tinymce.dom, 'data-mce-expando'); // Namespace and expando
5109 
5110 tinymce.dom.TreeWalker = function(start_node, root_node) {
5111 	var node = start_node;
5112 
5113 	function findSibling(node, start_name, sibling_name, shallow) {
5114 		var sibling, parent;
5115 
5116 		if (node) {
5117 			// Walk into nodes if it has a start
5118 			if (!shallow && node[start_name])
5119 				return node[start_name];
5120 
5121 			// Return the sibling if it has one
5122 			if (node != root_node) {
5123 				sibling = node[sibling_name];
5124 				if (sibling)
5125 					return sibling;
5126 
5127 				// Walk up the parents to look for siblings
5128 				for (parent = node.parentNode; parent && parent != root_node; parent = parent.parentNode) {
5129 					sibling = parent[sibling_name];
5130 					if (sibling)
5131 						return sibling;
5132 				}
5133 			}
5134 		}
5135 	};
5136 
5137 	this.current = function() {
5138 		return node;
5139 	};
5140 
5141 	this.next = function(shallow) {
5142 		return (node = findSibling(node, 'firstChild', 'nextSibling', shallow));
5143 	};
5144 
5145 	this.prev = function(shallow) {
5146 		return (node = findSibling(node, 'lastChild', 'previousSibling', shallow));
5147 	};
5148 };
5149 
5150 (function(tinymce) {
5151 	// Shorten names
5152 	var each = tinymce.each,
5153 		is = tinymce.is,
5154 		isWebKit = tinymce.isWebKit,
5155 		isIE = tinymce.isIE,
5156 		Entities = tinymce.html.Entities,
5157 		simpleSelectorRe = /^([a-z0-9],?)+$/i,
5158 		whiteSpaceRegExp = /^[ \t\r\n]*$/;
5159 
5160 	tinymce.create('tinymce.dom.DOMUtils', {
5161 		doc : null,
5162 		root : null,
5163 		files : null,
5164 		pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
5165 		props : {
5166 			"for" : "htmlFor",
5167 			"class" : "className",
5168 			className : "className",
5169 			checked : "checked",
5170 			disabled : "disabled",
5171 			maxlength : "maxLength",
5172 			readonly : "readOnly",
5173 			selected : "selected",
5174 			value : "value",
5175 			id : "id",
5176 			name : "name",
5177 			type : "type"
5178 		},
5179 
5180 		DOMUtils : function(d, s) {
5181 			var t = this, globalStyle, name, blockElementsMap;
5182 
5183 			t.doc = d;
5184 			t.win = window;
5185 			t.files = {};
5186 			t.cssFlicker = false;
5187 			t.counter = 0;
5188 			t.stdMode = !tinymce.isIE || d.documentMode >= 8;
5189 			t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
5190 			t.hasOuterHTML = "outerHTML" in d.createElement("a");
5191 
5192 			t.settings = s = tinymce.extend({
5193 				keep_values : false,
5194 				hex_colors : 1
5195 			}, s);
5196 			
5197 			t.schema = s.schema;
5198 			t.styles = new tinymce.html.Styles({
5199 				url_converter : s.url_converter,
5200 				url_converter_scope : s.url_converter_scope
5201 			}, s.schema);
5202 
5203 			// Fix IE6SP2 flicker and check it failed for pre SP2
5204 			if (tinymce.isIE6) {
5205 				try {
5206 					d.execCommand('BackgroundImageCache', false, true);
5207 				} catch (e) {
5208 					t.cssFlicker = true;
5209 				}
5210 			}
5211 
5212 			t.fixDoc(d);
5213 			t.events = s.ownEvents ? new tinymce.dom.EventUtils(s.proxy) : tinymce.dom.Event;
5214 			tinymce.addUnload(t.destroy, t);
5215 			blockElementsMap = s.schema ? s.schema.getBlockElements() : {};
5216 
5217 			t.isBlock = function(node) {
5218 				// This function is called in module pattern style since it might be executed with the wrong this scope
5219 				var type = node.nodeType;
5220 
5221 				// If it's a node then check the type and use the nodeName
5222 				if (type)
5223 					return !!(type === 1 && blockElementsMap[node.nodeName]);
5224 
5225 				return !!blockElementsMap[node];
5226 			};
5227 		},
5228 
5229 		fixDoc: function(doc) {
5230 			var settings = this.settings, name;
5231 
5232 			if (isIE && settings.schema) {
5233 				// Add missing HTML 4/5 elements to IE
5234 				('abbr article aside audio canvas ' +
5235 				'details figcaption figure footer ' +
5236 				'header hgroup mark menu meter nav ' +
5237 				'output progress section summary ' +
5238 				'time video').replace(/\w+/g, function(name) {
5239 					doc.createElement(name);
5240 				});
5241 
5242 				// Create all custom elements
5243 				for (name in settings.schema.getCustomElements()) {
5244 					doc.createElement(name);
5245 				}
5246 			}
5247 		},
5248 
5249 		clone: function(node, deep) {
5250 			var self = this, clone, doc;
5251 
5252 			// TODO: Add feature detection here in the future
5253 			if (!isIE || node.nodeType !== 1 || deep) {
5254 				return node.cloneNode(deep);
5255 			}
5256 
5257 			doc = self.doc;
5258 
5259 			// Make a HTML5 safe shallow copy
5260 			if (!deep) {
5261 				clone = doc.createElement(node.nodeName);
5262 
5263 				// Copy attribs
5264 				each(self.getAttribs(node), function(attr) {
5265 					self.setAttrib(clone, attr.nodeName, self.getAttrib(node, attr.nodeName));
5266 				});
5267 
5268 				return clone;
5269 			}
5270 /*
5271 			// Setup HTML5 patched document fragment
5272 			if (!self.frag) {
5273 				self.frag = doc.createDocumentFragment();
5274 				self.fixDoc(self.frag);
5275 			}
5276 
5277 			// Make a deep copy by adding it to the document fragment then removing it this removed the :section
5278 			clone = doc.createElement('div');
5279 			self.frag.appendChild(clone);
5280 			clone.innerHTML = node.outerHTML;
5281 			self.frag.removeChild(clone);
5282 */
5283 			return clone.firstChild;
5284 		},
5285 
5286 		getRoot : function() {
5287 			var t = this, s = t.settings;
5288 
5289 			return (s && t.get(s.root_element)) || t.doc.body;
5290 		},
5291 
5292 		getViewPort : function(w) {
5293 			var d, b;
5294 
5295 			w = !w ? this.win : w;
5296 			d = w.document;
5297 			b = this.boxModel ? d.documentElement : d.body;
5298 
5299 			// Returns viewport size excluding scrollbars
5300 			return {
5301 				x : w.pageXOffset || b.scrollLeft,
5302 				y : w.pageYOffset || b.scrollTop,
5303 				w : w.innerWidth || b.clientWidth,
5304 				h : w.innerHeight || b.clientHeight
5305 			};
5306 		},
5307 
5308 		getRect : function(e) {
5309 			var p, t = this, sr;
5310 
5311 			e = t.get(e);
5312 			p = t.getPos(e);
5313 			sr = t.getSize(e);
5314 
5315 			return {
5316 				x : p.x,
5317 				y : p.y,
5318 				w : sr.w,
5319 				h : sr.h
5320 			};
5321 		},
5322 
5323 		getSize : function(e) {
5324 			var t = this, w, h;
5325 
5326 			e = t.get(e);
5327 			w = t.getStyle(e, 'width');
5328 			h = t.getStyle(e, 'height');
5329 
5330 			// Non pixel value, then force offset/clientWidth
5331 			if (w.indexOf('px') === -1)
5332 				w = 0;
5333 
5334 			// Non pixel value, then force offset/clientWidth
5335 			if (h.indexOf('px') === -1)
5336 				h = 0;
5337 
5338 			return {
5339 				w : parseInt(w, 10) || e.offsetWidth || e.clientWidth,
5340 				h : parseInt(h, 10) || e.offsetHeight || e.clientHeight
5341 			};
5342 		},
5343 
5344 		getParent : function(n, f, r) {
5345 			return this.getParents(n, f, r, false);
5346 		},
5347 
5348 		getParents : function(n, f, r, c) {
5349 			var t = this, na, se = t.settings, o = [];
5350 
5351 			n = t.get(n);
5352 			c = c === undefined;
5353 
5354 			if (se.strict_root)
5355 				r = r || t.getRoot();
5356 
5357 			// Wrap node name as func
5358 			if (is(f, 'string')) {
5359 				na = f;
5360 
5361 				if (f === '*') {
5362 					f = function(n) {return n.nodeType == 1;};
5363 				} else {
5364 					f = function(n) {
5365 						return t.is(n, na);
5366 					};
5367 				}
5368 			}
5369 
5370 			while (n) {
5371 				if (n == r || !n.nodeType || n.nodeType === 9)
5372 					break;
5373 
5374 				if (!f || f(n)) {
5375 					if (c)
5376 						o.push(n);
5377 					else
5378 						return n;
5379 				}
5380 
5381 				n = n.parentNode;
5382 			}
5383 
5384 			return c ? o : null;
5385 		},
5386 
5387 		get : function(e) {
5388 			var n;
5389 
5390 			if (e && this.doc && typeof(e) == 'string') {
5391 				n = e;
5392 				e = this.doc.getElementById(e);
5393 
5394 				// IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
5395 				if (e && e.id !== n)
5396 					return this.doc.getElementsByName(n)[1];
5397 			}
5398 
5399 			return e;
5400 		},
5401 
5402 		getNext : function(node, selector) {
5403 			return this._findSib(node, selector, 'nextSibling');
5404 		},
5405 
5406 		getPrev : function(node, selector) {
5407 			return this._findSib(node, selector, 'previousSibling');
5408 		},
5409 
5410 
5411 		add : function(p, n, a, h, c) {
5412 			var t = this;
5413 
5414 			return this.run(p, function(p) {
5415 				var e, k;
5416 
5417 				e = is(n, 'string') ? t.doc.createElement(n) : n;
5418 				t.setAttribs(e, a);
5419 
5420 				if (h) {
5421 					if (h.nodeType)
5422 						e.appendChild(h);
5423 					else
5424 						t.setHTML(e, h);
5425 				}
5426 
5427 				return !c ? p.appendChild(e) : e;
5428 			});
5429 		},
5430 
5431 		create : function(n, a, h) {
5432 			return this.add(this.doc.createElement(n), n, a, h, 1);
5433 		},
5434 
5435 		createHTML : function(n, a, h) {
5436 			var o = '', t = this, k;
5437 
5438 			o += '<' + n;
5439 
5440 			for (k in a) {
5441 				if (a.hasOwnProperty(k))
5442 					o += ' ' + k + '="' + t.encode(a[k]) + '"';
5443 			}
5444 
5445 			// A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
5446 			if (typeof(h) != "undefined")
5447 				return o + '>' + h + '</' + n + '>';
5448 
5449 			return o + ' />';
5450 		},
5451 
5452 		remove : function(node, keep_children) {
5453 			return this.run(node, function(node) {
5454 				var child, parent = node.parentNode;
5455 
5456 				if (!parent)
5457 					return null;
5458 
5459 				if (keep_children) {
5460 					while (child = node.firstChild) {
5461 						// IE 8 will crash if you don't remove completely empty text nodes
5462 						if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
5463 							parent.insertBefore(child, node);
5464 						else
5465 							node.removeChild(child);
5466 					}
5467 				}
5468 
5469 				return parent.removeChild(node);
5470 			});
5471 		},
5472 
5473 		setStyle : function(n, na, v) {
5474 			var t = this;
5475 
5476 			return t.run(n, function(e) {
5477 				var s, i;
5478 
5479 				s = e.style;
5480 
5481 				// Camelcase it, if needed
5482 				na = na.replace(/-(\D)/g, function(a, b){
5483 					return b.toUpperCase();
5484 				});
5485 
5486 				// Default px suffix on these
5487 				if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
5488 					v += 'px';
5489 
5490 				switch (na) {
5491 					case 'opacity':
5492 						// IE specific opacity
5493 						if (isIE) {
5494 							s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
5495 
5496 							if (!n.currentStyle || !n.currentStyle.hasLayout)
5497 								s.display = 'inline-block';
5498 						}
5499 
5500 						// Fix for older browsers
5501 						s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
5502 						break;
5503 
5504 					case 'float':
5505 						isIE ? s.styleFloat = v : s.cssFloat = v;
5506 						break;
5507 					
5508 					default:
5509 						s[na] = v || '';
5510 				}
5511 
5512 				// Force update of the style data
5513 				if (t.settings.update_styles)
5514 					t.setAttrib(e, 'data-mce-style');
5515 			});
5516 		},
5517 
5518 		getStyle : function(n, na, c) {
5519 			n = this.get(n);
5520 
5521 			if (!n)
5522 				return;
5523 
5524 			// Gecko
5525 			if (this.doc.defaultView && c) {
5526 				// Remove camelcase
5527 				na = na.replace(/[A-Z]/g, function(a){
5528 					return '-' + a;
5529 				});
5530 
5531 				try {
5532 					return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
5533 				} catch (ex) {
5534 					// Old safari might fail
5535 					return null;
5536 				}
5537 			}
5538 
5539 			// Camelcase it, if needed
5540 			na = na.replace(/-(\D)/g, function(a, b){
5541 				return b.toUpperCase();
5542 			});
5543 
5544 			if (na == 'float')
5545 				na = isIE ? 'styleFloat' : 'cssFloat';
5546 
5547 			// IE & Opera
5548 			if (n.currentStyle && c)
5549 				return n.currentStyle[na];
5550 
5551 			return n.style ? n.style[na] : undefined;
5552 		},
5553 
5554 		setStyles : function(e, o) {
5555 			var t = this, s = t.settings, ol;
5556 
5557 			ol = s.update_styles;
5558 			s.update_styles = 0;
5559 
5560 			each(o, function(v, n) {
5561 				t.setStyle(e, n, v);
5562 			});
5563 
5564 			// Update style info
5565 			s.update_styles = ol;
5566 			if (s.update_styles)
5567 				t.setAttrib(e, s.cssText);
5568 		},
5569 
5570 		removeAllAttribs: function(e) {
5571 			return this.run(e, function(e) {
5572 				var i, attrs = e.attributes;
5573 				for (i = attrs.length - 1; i >= 0; i--) {
5574 					e.removeAttributeNode(attrs.item(i));
5575 				}
5576 			});
5577 		},
5578 
5579 		setAttrib : function(e, n, v) {
5580 			var t = this;
5581 
5582 			// Whats the point
5583 			if (!e || !n)
5584 				return;
5585 
5586 			// Strict XML mode
5587 			if (t.settings.strict)
5588 				n = n.toLowerCase();
5589 
5590 			return this.run(e, function(e) {
5591 				var s = t.settings;
5592 				var originalValue = e.getAttribute(n);
5593 				if (v !== null) {
5594 					switch (n) {
5595 						case "style":
5596 							if (!is(v, 'string')) {
5597 								each(v, function(v, n) {
5598 									t.setStyle(e, n, v);
5599 								});
5600 
5601 								return;
5602 							}
5603 
5604 							// No mce_style for elements with these since they might get resized by the user
5605 							if (s.keep_values) {
5606 								if (v && !t._isRes(v))
5607 									e.setAttribute('data-mce-style', v, 2);
5608 								else
5609 									e.removeAttribute('data-mce-style', 2);
5610 							}
5611 
5612 							e.style.cssText = v;
5613 							break;
5614 
5615 						case "class":
5616 							e.className = v || ''; // Fix IE null bug
5617 							break;
5618 
5619 						case "src":
5620 						case "href":
5621 							if (s.keep_values) {
5622 								if (s.url_converter)
5623 									v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
5624 
5625 								t.setAttrib(e, 'data-mce-' + n, v, 2);
5626 							}
5627 
5628 							break;
5629 
5630 						case "shape":
5631 							e.setAttribute('data-mce-style', v);
5632 							break;
5633 					}
5634 				}
5635 				if (is(v) && v !== null && v.length !== 0)
5636 					e.setAttribute(n, '' + v, 2);
5637 				else
5638 					e.removeAttribute(n, 2);
5639 
5640 				// fire onChangeAttrib event for attributes that have changed
5641 				if (tinyMCE.activeEditor && originalValue != v) {
5642 					var ed = tinyMCE.activeEditor;
5643 					ed.onSetAttrib.dispatch(ed, e, n, v);
5644 				}
5645 			});
5646 		},
5647 
5648 		setAttribs : function(e, o) {
5649 			var t = this;
5650 
5651 			return this.run(e, function(e) {
5652 				each(o, function(v, n) {
5653 					t.setAttrib(e, n, v);
5654 				});
5655 			});
5656 		},
5657 
5658 		getAttrib : function(e, n, dv) {
5659 			var v, t = this, undef;
5660 
5661 			e = t.get(e);
5662 
5663 			if (!e || e.nodeType !== 1)
5664 				return dv === undef ? false : dv;
5665 
5666 			if (!is(dv))
5667 				dv = '';
5668 
5669 			// Try the mce variant for these
5670 			if (/^(src|href|style|coords|shape)$/.test(n)) {
5671 				v = e.getAttribute("data-mce-" + n);
5672 
5673 				if (v)
5674 					return v;
5675 			}
5676 
5677 			if (isIE && t.props[n]) {
5678 				v = e[t.props[n]];
5679 				v = v && v.nodeValue ? v.nodeValue : v;
5680 			}
5681 
5682 			if (!v)
5683 				v = e.getAttribute(n, 2);
5684 
5685 			// Check boolean attribs
5686 			if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
5687 				if (e[t.props[n]] === true && v === '')
5688 					return n;
5689 
5690 				return v ? n : '';
5691 			}
5692 
5693 			// Inner input elements will override attributes on form elements
5694 			if (e.nodeName === "FORM" && e.getAttributeNode(n))
5695 				return e.getAttributeNode(n).nodeValue;
5696 
5697 			if (n === 'style') {
5698 				v = v || e.style.cssText;
5699 
5700 				if (v) {
5701 					v = t.serializeStyle(t.parseStyle(v), e.nodeName);
5702 
5703 					if (t.settings.keep_values && !t._isRes(v))
5704 						e.setAttribute('data-mce-style', v);
5705 				}
5706 			}
5707 
5708 			// Remove Apple and WebKit stuff
5709 			if (isWebKit && n === "class" && v)
5710 				v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
5711 
5712 			// Handle IE issues
5713 			if (isIE) {
5714 				switch (n) {
5715 					case 'rowspan':
5716 					case 'colspan':
5717 						// IE returns 1 as default value
5718 						if (v === 1)
5719 							v = '';
5720 
5721 						break;
5722 
5723 					case 'size':
5724 						// IE returns +0 as default value for size
5725 						if (v === '+0' || v === 20 || v === 0)
5726 							v = '';
5727 
5728 						break;
5729 
5730 					case 'width':
5731 					case 'height':
5732 					case 'vspace':
5733 					case 'checked':
5734 					case 'disabled':
5735 					case 'readonly':
5736 						if (v === 0)
5737 							v = '';
5738 
5739 						break;
5740 
5741 					case 'hspace':
5742 						// IE returns -1 as default value
5743 						if (v === -1)
5744 							v = '';
5745 
5746 						break;
5747 
5748 					case 'maxlength':
5749 					case 'tabindex':
5750 						// IE returns default value
5751 						if (v === 32768 || v === 2147483647 || v === '32768')
5752 							v = '';
5753 
5754 						break;
5755 
5756 					case 'multiple':
5757 					case 'compact':
5758 					case 'noshade':
5759 					case 'nowrap':
5760 						if (v === 65535)
5761 							return n;
5762 
5763 						return dv;
5764 
5765 					case 'shape':
5766 						v = v.toLowerCase();
5767 						break;
5768 
5769 					default:
5770 						// IE has odd anonymous function for event attributes
5771 						if (n.indexOf('on') === 0 && v)
5772 							v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
5773 				}
5774 			}
5775 
5776 			return (v !== undef && v !== null && v !== '') ? '' + v : dv;
5777 		},
5778 
5779 		getPos : function(n, ro) {
5780 			var t = this, x = 0, y = 0, e, d = t.doc, r;
5781 
5782 			n = t.get(n);
5783 			ro = ro || d.body;
5784 
5785 			if (n) {
5786 				// Use getBoundingClientRect if it exists since it's faster than looping offset nodes
5787 				if (n.getBoundingClientRect) {
5788 					n = n.getBoundingClientRect();
5789 					e = t.boxModel ? d.documentElement : d.body;
5790 
5791 					// Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
5792 					// Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
5793 					x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
5794 					y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
5795 
5796 					return {x : x, y : y};
5797 				}
5798 
5799 				r = n;
5800 				while (r && r != ro && r.nodeType) {
5801 					x += r.offsetLeft || 0;
5802 					y += r.offsetTop || 0;
5803 					r = r.offsetParent;
5804 				}
5805 
5806 				r = n.parentNode;
5807 				while (r && r != ro && r.nodeType) {
5808 					x -= r.scrollLeft || 0;
5809 					y -= r.scrollTop || 0;
5810 					r = r.parentNode;
5811 				}
5812 			}
5813 
5814 			return {x : x, y : y};
5815 		},
5816 
5817 		parseStyle : function(st) {
5818 			return this.styles.parse(st);
5819 		},
5820 
5821 		serializeStyle : function(o, name) {
5822 			return this.styles.serialize(o, name);
5823 		},
5824 
5825 		addStyle: function(cssText) {
5826 			var doc = this.doc, head;
5827 
5828 			// Create style element if needed
5829 			styleElm = doc.getElementById('mceDefaultStyles');
5830 			if (!styleElm) {
5831 				styleElm = doc.createElement('style'),
5832 				styleElm.id = 'mceDefaultStyles';
5833 				styleElm.type = 'text/css';
5834 
5835 				head = doc.getElementsByTagName('head')[0]
5836 				if (head.firstChild) {
5837 					head.insertBefore(styleElm, head.firstChild);
5838 				} else {
5839 					head.appendChild(styleElm);
5840 				}
5841 			}
5842 
5843 			// Append style data to old or new style element
5844 			if (styleElm.styleSheet) {
5845 				styleElm.styleSheet.cssText += cssText;
5846 			} else {
5847 				styleElm.appendChild(doc.createTextNode(cssText));
5848 			}
5849 		},
5850 
5851 		loadCSS : function(u) {
5852 			var t = this, d = t.doc, head;
5853 
5854 			if (!u)
5855 				u = '';
5856 
5857 			head = d.getElementsByTagName('head')[0];
5858 
5859 			each(u.split(','), function(u) {
5860 				var link;
5861 
5862 				if (t.files[u])
5863 					return;
5864 
5865 				t.files[u] = true;
5866 				link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
5867 
5868 				// IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
5869 				// This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
5870 				// It's ugly but it seems to work fine.
5871 				if (isIE && d.documentMode && d.recalc) {
5872 					link.onload = function() {
5873 						if (d.recalc)
5874 							d.recalc();
5875 
5876 						link.onload = null;
5877 					};
5878 				}
5879 
5880 				head.appendChild(link);
5881 			});
5882 		},
5883 
5884 		addClass : function(e, c) {
5885 			return this.run(e, function(e) {
5886 				var o;
5887 
5888 				if (!c)
5889 					return 0;
5890 
5891 				if (this.hasClass(e, c))
5892 					return e.className;
5893 
5894 				o = this.removeClass(e, c);
5895 
5896 				return e.className = (o != '' ? (o + ' ') : '') + c;
5897 			});
5898 		},
5899 
5900 		removeClass : function(e, c) {
5901 			var t = this, re;
5902 
5903 			return t.run(e, function(e) {
5904 				var v;
5905 
5906 				if (t.hasClass(e, c)) {
5907 					if (!re)
5908 						re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
5909 
5910 					v = e.className.replace(re, ' ');
5911 					v = tinymce.trim(v != ' ' ? v : '');
5912 
5913 					e.className = v;
5914 
5915 					// Empty class attr
5916 					if (!v) {
5917 						e.removeAttribute('class');
5918 						e.removeAttribute('className');
5919 					}
5920 
5921 					return v;
5922 				}
5923 
5924 				return e.className;
5925 			});
5926 		},
5927 
5928 		hasClass : function(n, c) {
5929 			n = this.get(n);
5930 
5931 			if (!n || !c)
5932 				return false;
5933 
5934 			return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
5935 		},
5936 
5937 		show : function(e) {
5938 			return this.setStyle(e, 'display', 'block');
5939 		},
5940 
5941 		hide : function(e) {
5942 			return this.setStyle(e, 'display', 'none');
5943 		},
5944 
5945 		isHidden : function(e) {
5946 			e = this.get(e);
5947 
5948 			return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
5949 		},
5950 
5951 		uniqueId : function(p) {
5952 			return (!p ? 'mce_' : p) + (this.counter++);
5953 		},
5954 
5955 		setHTML : function(element, html) {
5956 			var self = this;
5957 
5958 			return self.run(element, function(element) {
5959 				if (isIE) {
5960 					// Remove all child nodes, IE keeps empty text nodes in DOM
5961 					while (element.firstChild)
5962 						element.removeChild(element.firstChild);
5963 
5964 					try {
5965 						// IE will remove comments from the beginning
5966 						// unless you padd the contents with something
5967 						element.innerHTML = '<br />' + html;
5968 						element.removeChild(element.firstChild);
5969 					} catch (ex) {
5970 						// IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
5971 						// This seems to fix this problem
5972 
5973 						// Create new div with HTML contents and a BR infront to keep comments
5974 						var newElement = self.create('div');
5975 						newElement.innerHTML = '<br />' + html;
5976 
5977 						// Add all children from div to target
5978 						each (tinymce.grep(newElement.childNodes), function(node, i) {
5979 							// Skip br element
5980 							if (i && element.canHaveHTML)
5981 								element.appendChild(node);
5982 						});
5983 					}
5984 				} else
5985 					element.innerHTML = html;
5986 
5987 				return html;
5988 			});
5989 		},
5990 
5991 		getOuterHTML : function(elm) {
5992 			var doc, self = this;
5993 
5994 			elm = self.get(elm);
5995 
5996 			if (!elm)
5997 				return null;
5998 
5999 			if (elm.nodeType === 1 && self.hasOuterHTML)
6000 				return elm.outerHTML;
6001 
6002 			doc = (elm.ownerDocument || self.doc).createElement("body");
6003 			doc.appendChild(elm.cloneNode(true));
6004 
6005 			return doc.innerHTML;
6006 		},
6007 
6008 		setOuterHTML : function(e, h, d) {
6009 			var t = this;
6010 
6011 			function setHTML(e, h, d) {
6012 				var n, tp;
6013 
6014 				tp = d.createElement("body");
6015 				tp.innerHTML = h;
6016 
6017 				n = tp.lastChild;
6018 				while (n) {
6019 					t.insertAfter(n.cloneNode(true), e);
6020 					n = n.previousSibling;
6021 				}
6022 
6023 				t.remove(e);
6024 			};
6025 
6026 			return this.run(e, function(e) {
6027 				e = t.get(e);
6028 
6029 				// Only set HTML on elements
6030 				if (e.nodeType == 1) {
6031 					d = d || e.ownerDocument || t.doc;
6032 
6033 					if (isIE) {
6034 						try {
6035 							// Try outerHTML for IE it sometimes produces an unknown runtime error
6036 							if (isIE && e.nodeType == 1)
6037 								e.outerHTML = h;
6038 							else
6039 								setHTML(e, h, d);
6040 						} catch (ex) {
6041 							// Fix for unknown runtime error
6042 							setHTML(e, h, d);
6043 						}
6044 					} else
6045 						setHTML(e, h, d);
6046 				}
6047 			});
6048 		},
6049 
6050 		decode : Entities.decode,
6051 
6052 		encode : Entities.encodeAllRaw,
6053 
6054 		insertAfter : function(node, reference_node) {
6055 			reference_node = this.get(reference_node);
6056 
6057 			return this.run(node, function(node) {
6058 				var parent, nextSibling;
6059 
6060 				parent = reference_node.parentNode;
6061 				nextSibling = reference_node.nextSibling;
6062 
6063 				if (nextSibling)
6064 					parent.insertBefore(node, nextSibling);
6065 				else
6066 					parent.appendChild(node);
6067 
6068 				return node;
6069 			});
6070 		},
6071 
6072 		replace : function(n, o, k) {
6073 			var t = this;
6074 
6075 			if (is(o, 'array'))
6076 				n = n.cloneNode(true);
6077 
6078 			return t.run(o, function(o) {
6079 				if (k) {
6080 					each(tinymce.grep(o.childNodes), function(c) {
6081 						n.appendChild(c);
6082 					});
6083 				}
6084 
6085 				return o.parentNode.replaceChild(n, o);
6086 			});
6087 		},
6088 
6089 		rename : function(elm, name) {
6090 			var t = this, newElm;
6091 
6092 			if (elm.nodeName != name.toUpperCase()) {
6093 				// Rename block element
6094 				newElm = t.create(name);
6095 
6096 				// Copy attribs to new block
6097 				each(t.getAttribs(elm), function(attr_node) {
6098 					t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
6099 				});
6100 
6101 				// Replace block
6102 				t.replace(newElm, elm, 1);
6103 			}
6104 
6105 			return newElm || elm;
6106 		},
6107 
6108 		findCommonAncestor : function(a, b) {
6109 			var ps = a, pe;
6110 
6111 			while (ps) {
6112 				pe = b;
6113 
6114 				while (pe && ps != pe)
6115 					pe = pe.parentNode;
6116 
6117 				if (ps == pe)
6118 					break;
6119 
6120 				ps = ps.parentNode;
6121 			}
6122 
6123 			if (!ps && a.ownerDocument)
6124 				return a.ownerDocument.documentElement;
6125 
6126 			return ps;
6127 		},
6128 
6129 		toHex : function(s) {
6130 			var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
6131 
6132 			function hex(s) {
6133 				s = parseInt(s, 10).toString(16);
6134 
6135 				return s.length > 1 ? s : '0' + s; // 0 -> 00
6136 			};
6137 
6138 			if (c) {
6139 				s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
6140 
6141 				return s;
6142 			}
6143 
6144 			return s;
6145 		},
6146 
6147 		getClasses : function() {
6148 			var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
6149 
6150 			if (t.classes)
6151 				return t.classes;
6152 
6153 			function addClasses(s) {
6154 				// IE style imports
6155 				each(s.imports, function(r) {
6156 					addClasses(r);
6157 				});
6158 
6159 				each(s.cssRules || s.rules, function(r) {
6160 					// Real type or fake it on IE
6161 					switch (r.type || 1) {
6162 						// Rule
6163 						case 1:
6164 							if (r.selectorText) {
6165 								each(r.selectorText.split(','), function(v) {
6166 									v = v.replace(/^\s*|\s*$|^\s\./g, "");
6167 
6168 									// Is internal or it doesn't contain a class
6169 									if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
6170 										return;
6171 
6172 									// Remove everything but class name
6173 									ov = v;
6174 									v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
6175 
6176 									// Filter classes
6177 									if (f && !(v = f(v, ov)))
6178 										return;
6179 
6180 									if (!lo[v]) {
6181 										cl.push({'class' : v});
6182 										lo[v] = 1;
6183 									}
6184 								});
6185 							}
6186 							break;
6187 
6188 						// Import
6189 						case 3:
6190 							addClasses(r.styleSheet);
6191 							break;
6192 					}
6193 				});
6194 			};
6195 
6196 			try {
6197 				each(t.doc.styleSheets, addClasses);
6198 			} catch (ex) {
6199 				// Ignore
6200 			}
6201 
6202 			if (cl.length > 0)
6203 				t.classes = cl;
6204 
6205 			return cl;
6206 		},
6207 
6208 		run : function(e, f, s) {
6209 			var t = this, o;
6210 
6211 			if (t.doc && typeof(e) === 'string')
6212 				e = t.get(e);
6213 
6214 			if (!e)
6215 				return false;
6216 
6217 			s = s || this;
6218 			if (!e.nodeType && (e.length || e.length === 0)) {
6219 				o = [];
6220 
6221 				each(e, function(e, i) {
6222 					if (e) {
6223 						if (typeof(e) == 'string')
6224 							e = t.doc.getElementById(e);
6225 
6226 						o.push(f.call(s, e, i));
6227 					}
6228 				});
6229 
6230 				return o;
6231 			}
6232 
6233 			return f.call(s, e);
6234 		},
6235 
6236 		getAttribs : function(n) {
6237 			var o;
6238 
6239 			n = this.get(n);
6240 
6241 			if (!n)
6242 				return [];
6243 
6244 			if (isIE) {
6245 				o = [];
6246 
6247 				// Object will throw exception in IE
6248 				if (n.nodeName == 'OBJECT')
6249 					return n.attributes;
6250 
6251 				// IE doesn't keep the selected attribute if you clone option elements
6252 				if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
6253 					o.push({specified : 1, nodeName : 'selected'});
6254 
6255 				// It's crazy that this is faster in IE but it's because it returns all attributes all the time
6256 				n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
6257 					o.push({specified : 1, nodeName : a});
6258 				});
6259 
6260 				return o;
6261 			}
6262 
6263 			return n.attributes;
6264 		},
6265 
6266 		isEmpty : function(node, elements) {
6267 			var self = this, i, attributes, type, walker, name, brCount = 0;
6268 
6269 			node = node.firstChild;
6270 			if (node) {
6271 				walker = new tinymce.dom.TreeWalker(node, node.parentNode);
6272 				elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
6273 
6274 				do {
6275 					type = node.nodeType;
6276 
6277 					if (type === 1) {
6278 						// Ignore bogus elements
6279 						if (node.getAttribute('data-mce-bogus'))
6280 							continue;
6281 
6282 						// Keep empty elements like <img />
6283 						name = node.nodeName.toLowerCase();
6284 						if (elements && elements[name]) {
6285 							// Ignore single BR elements in blocks like <p><br /></p> or <p><span><br /></span></p>
6286 							if (name === 'br') {
6287 								brCount++;
6288 								continue;
6289 							}
6290 
6291 							return false;
6292 						}
6293 
6294 						// Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
6295 						attributes = self.getAttribs(node);
6296 						i = node.attributes.length;
6297 						while (i--) {
6298 							name = node.attributes[i].nodeName;
6299 							if (name === "name" || name === 'data-mce-bookmark')
6300 								return false;
6301 						}
6302 					}
6303 
6304 					// Keep comment nodes
6305 					if (type == 8)
6306 						return false;
6307 
6308 					// Keep non whitespace text nodes
6309 					if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
6310 						return false;
6311 				} while (node = walker.next());
6312 			}
6313 
6314 			return brCount <= 1;
6315 		},
6316 
6317 		destroy : function(s) {
6318 			var t = this;
6319 
6320 			t.win = t.doc = t.root = t.events = t.frag = null;
6321 
6322 			// Manual destroy then remove unload handler
6323 			if (!s)
6324 				tinymce.removeUnload(t.destroy);
6325 		},
6326 
6327 		createRng : function() {
6328 			var d = this.doc;
6329 
6330 			return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
6331 		},
6332 
6333 		nodeIndex : function(node, normalized) {
6334 			var idx = 0, lastNodeType, lastNode, nodeType;
6335 
6336 			if (node) {
6337 				for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
6338 					nodeType = node.nodeType;
6339 
6340 					// Normalize text nodes
6341 					if (normalized && nodeType == 3) {
6342 						if (nodeType == lastNodeType || !node.nodeValue.length)
6343 							continue;
6344 					}
6345 					idx++;
6346 					lastNodeType = nodeType;
6347 				}
6348 			}
6349 
6350 			return idx;
6351 		},
6352 
6353 		split : function(pe, e, re) {
6354 			var t = this, r = t.createRng(), bef, aft, pa;
6355 
6356 			// W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
6357 			// but we don't want that in our code since it serves no purpose for the end user
6358 			// For example if this is chopped:
6359 			//   <p>text 1<span><b>CHOP</b></span>text 2</p>
6360 			// would produce:
6361 			//   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
6362 			// this function will then trim of empty edges and produce:
6363 			//   <p>text 1</p><b>CHOP</b><p>text 2</p>
6364 			function trim(node) {
6365 				var i, children = node.childNodes, type = node.nodeType;
6366 
6367 				function surroundedBySpans(node) {
6368 					var previousIsSpan = node.previousSibling && node.previousSibling.nodeName == 'SPAN';
6369 					var nextIsSpan = node.nextSibling && node.nextSibling.nodeName == 'SPAN';
6370 					return previousIsSpan && nextIsSpan;
6371 				}
6372 
6373 				if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
6374 					return;
6375 
6376 				for (i = children.length - 1; i >= 0; i--)
6377 					trim(children[i]);
6378 
6379 				if (type != 9) {
6380 					// Keep non whitespace text nodes
6381 					if (type == 3 && node.nodeValue.length > 0) {
6382 						// If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
6383 						// Also keep text nodes with only spaces if surrounded by spans.
6384 						// eg. "<p><span>a</span> <span>b</span></p>" should keep space between a and b
6385 						var trimmedLength = tinymce.trim(node.nodeValue).length;
6386 						if (!t.isBlock(node.parentNode) || trimmedLength > 0 || trimmedLength === 0 && surroundedBySpans(node))
6387 							return;
6388 					} else if (type == 1) {
6389 						// If the only child is a bookmark then move it up
6390 						children = node.childNodes;
6391 						if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
6392 							node.parentNode.insertBefore(children[0], node);
6393 
6394 						// Keep non empty elements or img, hr etc
6395 						if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
6396 							return;
6397 					}
6398 
6399 					t.remove(node);
6400 				}
6401 
6402 				return node;
6403 			};
6404 
6405 			if (pe && e) {
6406 				// Get before chunk
6407 				r.setStart(pe.parentNode, t.nodeIndex(pe));
6408 				r.setEnd(e.parentNode, t.nodeIndex(e));
6409 				bef = r.extractContents();
6410 
6411 				// Get after chunk
6412 				r = t.createRng();
6413 				r.setStart(e.parentNode, t.nodeIndex(e) + 1);
6414 				r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
6415 				aft = r.extractContents();
6416 
6417 				// Insert before chunk
6418 				pa = pe.parentNode;
6419 				pa.insertBefore(trim(bef), pe);
6420 
6421 				// Insert middle chunk
6422 				if (re)
6423 				pa.replaceChild(re, e);
6424 			else
6425 				pa.insertBefore(e, pe);
6426 
6427 				// Insert after chunk
6428 				pa.insertBefore(trim(aft), pe);
6429 				t.remove(pe);
6430 
6431 				return re || e;
6432 			}
6433 		},
6434 
6435 		bind : function(target, name, func, scope) {
6436 			return this.events.add(target, name, func, scope || this);
6437 		},
6438 
6439 		unbind : function(target, name, func) {
6440 			return this.events.remove(target, name, func);
6441 		},
6442 
6443 		fire : function(target, name, evt) {
6444 			return this.events.fire(target, name, evt);
6445 		},
6446 
6447 		// Returns the content editable state of a node
6448 		getContentEditable: function(node) {
6449 			var contentEditable;
6450 
6451 			// Check type
6452 			if (node.nodeType != 1) {
6453 				return null;
6454 			}
6455 
6456 			// Check for fake content editable
6457 			contentEditable = node.getAttribute("data-mce-contenteditable");
6458 			if (contentEditable && contentEditable !== "inherit") {
6459 				return contentEditable;
6460 			}
6461 
6462 			// Check for real content editable
6463 			return node.contentEditable !== "inherit" ? node.contentEditable : null;
6464 		},
6465 
6466 
6467 		_findSib : function(node, selector, name) {
6468 			var t = this, f = selector;
6469 
6470 			if (node) {
6471 				// If expression make a function of it using is
6472 				if (is(f, 'string')) {
6473 					f = function(node) {
6474 						return t.is(node, selector);
6475 					};
6476 				}
6477 
6478 				// Loop all siblings
6479 				for (node = node[name]; node; node = node[name]) {
6480 					if (f(node))
6481 						return node;
6482 				}
6483 			}
6484 
6485 			return null;
6486 		},
6487 
6488 		_isRes : function(c) {
6489 			// Is live resizble element
6490 			return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
6491 		}
6492 
6493 		/*
6494 		walk : function(n, f, s) {
6495 			var d = this.doc, w;
6496 
6497 			if (d.createTreeWalker) {
6498 				w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
6499 
6500 				while ((n = w.nextNode()) != null)
6501 					f.call(s || this, n);
6502 			} else
6503 				tinymce.walk(n, f, 'childNodes', s);
6504 		}
6505 		*/
6506 
6507 		/*
6508 		toRGB : function(s) {
6509 			var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
6510 
6511 			if (c) {
6512 				// #FFF -> #FFFFFF
6513 				if (!is(c[3]))
6514 					c[3] = c[2] = c[1];
6515 
6516 				return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
6517 			}
6518 
6519 			return s;
6520 		}
6521 		*/
6522 	});
6523 
6524 	tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
6525 })(tinymce);
6526 
6527 (function(ns) {
6528 	// Range constructor
6529 	function Range(dom) {
6530 		var t = this,
6531 			doc = dom.doc,
6532 			EXTRACT = 0,
6533 			CLONE = 1,
6534 			DELETE = 2,
6535 			TRUE = true,
6536 			FALSE = false,
6537 			START_OFFSET = 'startOffset',
6538 			START_CONTAINER = 'startContainer',
6539 			END_CONTAINER = 'endContainer',
6540 			END_OFFSET = 'endOffset',
6541 			extend = tinymce.extend,
6542 			nodeIndex = dom.nodeIndex;
6543 
6544 		extend(t, {
6545 			// Inital states
6546 			startContainer : doc,
6547 			startOffset : 0,
6548 			endContainer : doc,
6549 			endOffset : 0,
6550 			collapsed : TRUE,
6551 			commonAncestorContainer : doc,
6552 
6553 			// Range constants
6554 			START_TO_START : 0,
6555 			START_TO_END : 1,
6556 			END_TO_END : 2,
6557 			END_TO_START : 3,
6558 
6559 			// Public methods
6560 			setStart : setStart,
6561 			setEnd : setEnd,
6562 			setStartBefore : setStartBefore,
6563 			setStartAfter : setStartAfter,
6564 			setEndBefore : setEndBefore,
6565 			setEndAfter : setEndAfter,
6566 			collapse : collapse,
6567 			selectNode : selectNode,
6568 			selectNodeContents : selectNodeContents,
6569 			compareBoundaryPoints : compareBoundaryPoints,
6570 			deleteContents : deleteContents,
6571 			extractContents : extractContents,
6572 			cloneContents : cloneContents,
6573 			insertNode : insertNode,
6574 			surroundContents : surroundContents,
6575 			cloneRange : cloneRange,
6576 			toStringIE : toStringIE
6577 		});
6578 
6579 		function createDocumentFragment() {
6580 			return doc.createDocumentFragment();
6581 		};
6582 
6583 		function setStart(n, o) {
6584 			_setEndPoint(TRUE, n, o);
6585 		};
6586 
6587 		function setEnd(n, o) {
6588 			_setEndPoint(FALSE, n, o);
6589 		};
6590 
6591 		function setStartBefore(n) {
6592 			setStart(n.parentNode, nodeIndex(n));
6593 		};
6594 
6595 		function setStartAfter(n) {
6596 			setStart(n.parentNode, nodeIndex(n) + 1);
6597 		};
6598 
6599 		function setEndBefore(n) {
6600 			setEnd(n.parentNode, nodeIndex(n));
6601 		};
6602 
6603 		function setEndAfter(n) {
6604 			setEnd(n.parentNode, nodeIndex(n) + 1);
6605 		};
6606 
6607 		function collapse(ts) {
6608 			if (ts) {
6609 				t[END_CONTAINER] = t[START_CONTAINER];
6610 				t[END_OFFSET] = t[START_OFFSET];
6611 			} else {
6612 				t[START_CONTAINER] = t[END_CONTAINER];
6613 				t[START_OFFSET] = t[END_OFFSET];
6614 			}
6615 
6616 			t.collapsed = TRUE;
6617 		};
6618 
6619 		function selectNode(n) {
6620 			setStartBefore(n);
6621 			setEndAfter(n);
6622 		};
6623 
6624 		function selectNodeContents(n) {
6625 			setStart(n, 0);
6626 			setEnd(n, n.nodeType === 1 ? n.childNodes.length : n.nodeValue.length);
6627 		};
6628 
6629 		function compareBoundaryPoints(h, r) {
6630 			var sc = t[START_CONTAINER], so = t[START_OFFSET], ec = t[END_CONTAINER], eo = t[END_OFFSET],
6631 			rsc = r.startContainer, rso = r.startOffset, rec = r.endContainer, reo = r.endOffset;
6632 
6633 			// Check START_TO_START
6634 			if (h === 0)
6635 				return _compareBoundaryPoints(sc, so, rsc, rso);
6636 	
6637 			// Check START_TO_END
6638 			if (h === 1)
6639 				return _compareBoundaryPoints(ec, eo, rsc, rso);
6640 	
6641 			// Check END_TO_END
6642 			if (h === 2)
6643 				return _compareBoundaryPoints(ec, eo, rec, reo);
6644 	
6645 			// Check END_TO_START
6646 			if (h === 3) 
6647 				return _compareBoundaryPoints(sc, so, rec, reo);
6648 		};
6649 
6650 		function deleteContents() {
6651 			_traverse(DELETE);
6652 		};
6653 
6654 		function extractContents() {
6655 			return _traverse(EXTRACT);
6656 		};
6657 
6658 		function cloneContents() {
6659 			return _traverse(CLONE);
6660 		};
6661 
6662 		function insertNode(n) {
6663 			var startContainer = this[START_CONTAINER],
6664 				startOffset = this[START_OFFSET], nn, o;
6665 
6666 			// Node is TEXT_NODE or CDATA
6667 			if ((startContainer.nodeType === 3 || startContainer.nodeType === 4) && startContainer.nodeValue) {
6668 				if (!startOffset) {
6669 					// At the start of text
6670 					startContainer.parentNode.insertBefore(n, startContainer);
6671 				} else if (startOffset >= startContainer.nodeValue.length) {
6672 					// At the end of text
6673 					dom.insertAfter(n, startContainer);
6674 				} else {
6675 					// Middle, need to split
6676 					nn = startContainer.splitText(startOffset);
6677 					startContainer.parentNode.insertBefore(n, nn);
6678 				}
6679 			} else {
6680 				// Insert element node
6681 				if (startContainer.childNodes.length > 0)
6682 					o = startContainer.childNodes[startOffset];
6683 
6684 				if (o)
6685 					startContainer.insertBefore(n, o);
6686 				else
6687 					startContainer.appendChild(n);
6688 			}
6689 		};
6690 
6691 		function surroundContents(n) {
6692 			var f = t.extractContents();
6693 
6694 			t.insertNode(n);
6695 			n.appendChild(f);
6696 			t.selectNode(n);
6697 		};
6698 
6699 		function cloneRange() {
6700 			return extend(new Range(dom), {
6701 				startContainer : t[START_CONTAINER],
6702 				startOffset : t[START_OFFSET],
6703 				endContainer : t[END_CONTAINER],
6704 				endOffset : t[END_OFFSET],
6705 				collapsed : t.collapsed,
6706 				commonAncestorContainer : t.commonAncestorContainer
6707 			});
6708 		};
6709 
6710 		// Private methods
6711 
6712 		function _getSelectedNode(container, offset) {
6713 			var child;
6714 
6715 			if (container.nodeType == 3 /* TEXT_NODE */)
6716 				return container;
6717 
6718 			if (offset < 0)
6719 				return container;
6720 
6721 			child = container.firstChild;
6722 			while (child && offset > 0) {
6723 				--offset;
6724 				child = child.nextSibling;
6725 			}
6726 
6727 			if (child)
6728 				return child;
6729 
6730 			return container;
6731 		};
6732 
6733 		function _isCollapsed() {
6734 			return (t[START_CONTAINER] == t[END_CONTAINER] && t[START_OFFSET] == t[END_OFFSET]);
6735 		};
6736 
6737 		function _compareBoundaryPoints(containerA, offsetA, containerB, offsetB) {
6738 			var c, offsetC, n, cmnRoot, childA, childB;
6739 			
6740 			// In the first case the boundary-points have the same container. A is before B
6741 			// if its offset is less than the offset of B, A is equal to B if its offset is
6742 			// equal to the offset of B, and A is after B if its offset is greater than the
6743 			// offset of B.
6744 			if (containerA == containerB) {
6745 				if (offsetA == offsetB)
6746 					return 0; // equal
6747 
6748 				if (offsetA < offsetB)
6749 					return -1; // before
6750 
6751 				return 1; // after
6752 			}
6753 
6754 			// In the second case a child node C of the container of A is an ancestor
6755 			// container of B. In this case, A is before B if the offset of A is less than or
6756 			// equal to the index of the child node C and A is after B otherwise.
6757 			c = containerB;
6758 			while (c && c.parentNode != containerA)
6759 				c = c.parentNode;
6760 
6761 			if (c) {
6762 				offsetC = 0;
6763 				n = containerA.firstChild;
6764 
6765 				while (n != c && offsetC < offsetA) {
6766 					offsetC++;
6767 					n = n.nextSibling;
6768 				}
6769 
6770 				if (offsetA <= offsetC)
6771 					return -1; // before
6772 
6773 				return 1; // after
6774 			}
6775 
6776 			// In the third case a child node C of the container of B is an ancestor container
6777 			// of A. In this case, A is before B if the index of the child node C is less than
6778 			// the offset of B and A is after B otherwise.
6779 			c = containerA;
6780 			while (c && c.parentNode != containerB) {
6781 				c = c.parentNode;
6782 			}
6783 
6784 			if (c) {
6785 				offsetC = 0;
6786 				n = containerB.firstChild;
6787 
6788 				while (n != c && offsetC < offsetB) {
6789 					offsetC++;
6790 					n = n.nextSibling;
6791 				}
6792 
6793 				if (offsetC < offsetB)
6794 					return -1; // before
6795 
6796 				return 1; // after
6797 			}
6798 
6799 			// In the fourth case, none of three other cases hold: the containers of A and B
6800 			// are siblings or descendants of sibling nodes. In this case, A is before B if
6801 			// the container of A is before the container of B in a pre-order traversal of the
6802 			// Ranges' context tree and A is after B otherwise.
6803 			cmnRoot = dom.findCommonAncestor(containerA, containerB);
6804 			childA = containerA;
6805 
6806 			while (childA && childA.parentNode != cmnRoot)
6807 				childA = childA.parentNode;
6808 
6809 			if (!childA)
6810 				childA = cmnRoot;
6811 
6812 			childB = containerB;
6813 			while (childB && childB.parentNode != cmnRoot)
6814 				childB = childB.parentNode;
6815 
6816 			if (!childB)
6817 				childB = cmnRoot;
6818 
6819 			if (childA == childB)
6820 				return 0; // equal
6821 
6822 			n = cmnRoot.firstChild;
6823 			while (n) {
6824 				if (n == childA)
6825 					return -1; // before
6826 
6827 				if (n == childB)
6828 					return 1; // after
6829 
6830 				n = n.nextSibling;
6831 			}
6832 		};
6833 
6834 		function _setEndPoint(st, n, o) {
6835 			var ec, sc;
6836 
6837 			if (st) {
6838 				t[START_CONTAINER] = n;
6839 				t[START_OFFSET] = o;
6840 			} else {
6841 				t[END_CONTAINER] = n;
6842 				t[END_OFFSET] = o;
6843 			}
6844 
6845 			// If one boundary-point of a Range is set to have a root container
6846 			// other than the current one for the Range, the Range is collapsed to
6847 			// the new position. This enforces the restriction that both boundary-
6848 			// points of a Range must have the same root container.
6849 			ec = t[END_CONTAINER];
6850 			while (ec.parentNode)
6851 				ec = ec.parentNode;
6852 
6853 			sc = t[START_CONTAINER];
6854 			while (sc.parentNode)
6855 				sc = sc.parentNode;
6856 
6857 			if (sc == ec) {
6858 				// The start position of a Range is guaranteed to never be after the
6859 				// end position. To enforce this restriction, if the start is set to
6860 				// be at a position after the end, the Range is collapsed to that
6861 				// position.
6862 				if (_compareBoundaryPoints(t[START_CONTAINER], t[START_OFFSET], t[END_CONTAINER], t[END_OFFSET]) > 0)
6863 					t.collapse(st);
6864 			} else
6865 				t.collapse(st);
6866 
6867 			t.collapsed = _isCollapsed();
6868 			t.commonAncestorContainer = dom.findCommonAncestor(t[START_CONTAINER], t[END_CONTAINER]);
6869 		};
6870 
6871 		function _traverse(how) {
6872 			var c, endContainerDepth = 0, startContainerDepth = 0, p, depthDiff, startNode, endNode, sp, ep;
6873 
6874 			if (t[START_CONTAINER] == t[END_CONTAINER])
6875 				return _traverseSameContainer(how);
6876 
6877 			for (c = t[END_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6878 				if (p == t[START_CONTAINER])
6879 					return _traverseCommonStartContainer(c, how);
6880 
6881 				++endContainerDepth;
6882 			}
6883 
6884 			for (c = t[START_CONTAINER], p = c.parentNode; p; c = p, p = p.parentNode) {
6885 				if (p == t[END_CONTAINER])
6886 					return _traverseCommonEndContainer(c, how);
6887 
6888 				++startContainerDepth;
6889 			}
6890 
6891 			depthDiff = startContainerDepth - endContainerDepth;
6892 
6893 			startNode = t[START_CONTAINER];
6894 			while (depthDiff > 0) {
6895 				startNode = startNode.parentNode;
6896 				depthDiff--;
6897 			}
6898 
6899 			endNode = t[END_CONTAINER];
6900 			while (depthDiff < 0) {
6901 				endNode = endNode.parentNode;
6902 				depthDiff++;
6903 			}
6904 
6905 			// ascend the ancestor hierarchy until we have a common parent.
6906 			for (sp = startNode.parentNode, ep = endNode.parentNode; sp != ep; sp = sp.parentNode, ep = ep.parentNode) {
6907 				startNode = sp;
6908 				endNode = ep;
6909 			}
6910 
6911 			return _traverseCommonAncestors(startNode, endNode, how);
6912 		};
6913 
6914 		 function _traverseSameContainer(how) {
6915 			var frag, s, sub, n, cnt, sibling, xferNode, start, len;
6916 
6917 			if (how != DELETE)
6918 				frag = createDocumentFragment();
6919 
6920 			// If selection is empty, just return the fragment
6921 			if (t[START_OFFSET] == t[END_OFFSET])
6922 				return frag;
6923 
6924 			// Text node needs special case handling
6925 			if (t[START_CONTAINER].nodeType == 3 /* TEXT_NODE */) {
6926 				// get the substring
6927 				s = t[START_CONTAINER].nodeValue;
6928 				sub = s.substring(t[START_OFFSET], t[END_OFFSET]);
6929 
6930 				// set the original text node to its new value
6931 				if (how != CLONE) {
6932 					n = t[START_CONTAINER];
6933 					start = t[START_OFFSET];
6934 					len = t[END_OFFSET] - t[START_OFFSET];
6935 
6936 					if (start === 0 && len >= n.nodeValue.length - 1) {
6937 						n.parentNode.removeChild(n);
6938 					} else {
6939 						n.deleteData(start, len);
6940 					}
6941 
6942 					// Nothing is partially selected, so collapse to start point
6943 					t.collapse(TRUE);
6944 				}
6945 
6946 				if (how == DELETE)
6947 					return;
6948 
6949 				if (sub.length > 0) {
6950 					frag.appendChild(doc.createTextNode(sub));
6951 				}
6952 
6953 				return frag;
6954 			}
6955 
6956 			// Copy nodes between the start/end offsets.
6957 			n = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]);
6958 			cnt = t[END_OFFSET] - t[START_OFFSET];
6959 
6960 			while (n && cnt > 0) {
6961 				sibling = n.nextSibling;
6962 				xferNode = _traverseFullySelected(n, how);
6963 
6964 				if (frag)
6965 					frag.appendChild( xferNode );
6966 
6967 				--cnt;
6968 				n = sibling;
6969 			}
6970 
6971 			// Nothing is partially selected, so collapse to start point
6972 			if (how != CLONE)
6973 				t.collapse(TRUE);
6974 
6975 			return frag;
6976 		};
6977 
6978 		function _traverseCommonStartContainer(endAncestor, how) {
6979 			var frag, n, endIdx, cnt, sibling, xferNode;
6980 
6981 			if (how != DELETE)
6982 				frag = createDocumentFragment();
6983 
6984 			n = _traverseRightBoundary(endAncestor, how);
6985 
6986 			if (frag)
6987 				frag.appendChild(n);
6988 
6989 			endIdx = nodeIndex(endAncestor);
6990 			cnt = endIdx - t[START_OFFSET];
6991 
6992 			if (cnt <= 0) {
6993 				// Collapse to just before the endAncestor, which
6994 				// is partially selected.
6995 				if (how != CLONE) {
6996 					t.setEndBefore(endAncestor);
6997 					t.collapse(FALSE);
6998 				}
6999 
7000 				return frag;
7001 			}
7002 
7003 			n = endAncestor.previousSibling;
7004 			while (cnt > 0) {
7005 				sibling = n.previousSibling;
7006 				xferNode = _traverseFullySelected(n, how);
7007 
7008 				if (frag)
7009 					frag.insertBefore(xferNode, frag.firstChild);
7010 
7011 				--cnt;
7012 				n = sibling;
7013 			}
7014 
7015 			// Collapse to just before the endAncestor, which
7016 			// is partially selected.
7017 			if (how != CLONE) {
7018 				t.setEndBefore(endAncestor);
7019 				t.collapse(FALSE);
7020 			}
7021 
7022 			return frag;
7023 		};
7024 
7025 		function _traverseCommonEndContainer(startAncestor, how) {
7026 			var frag, startIdx, n, cnt, sibling, xferNode;
7027 
7028 			if (how != DELETE)
7029 				frag = createDocumentFragment();
7030 
7031 			n = _traverseLeftBoundary(startAncestor, how);
7032 			if (frag)
7033 				frag.appendChild(n);
7034 
7035 			startIdx = nodeIndex(startAncestor);
7036 			++startIdx; // Because we already traversed it
7037 
7038 			cnt = t[END_OFFSET] - startIdx;
7039 			n = startAncestor.nextSibling;
7040 			while (n && cnt > 0) {
7041 				sibling = n.nextSibling;
7042 				xferNode = _traverseFullySelected(n, how);
7043 
7044 				if (frag)
7045 					frag.appendChild(xferNode);
7046 
7047 				--cnt;
7048 				n = sibling;
7049 			}
7050 
7051 			if (how != CLONE) {
7052 				t.setStartAfter(startAncestor);
7053 				t.collapse(TRUE);
7054 			}
7055 
7056 			return frag;
7057 		};
7058 
7059 		function _traverseCommonAncestors(startAncestor, endAncestor, how) {
7060 			var n, frag, commonParent, startOffset, endOffset, cnt, sibling, nextSibling;
7061 
7062 			if (how != DELETE)
7063 				frag = createDocumentFragment();
7064 
7065 			n = _traverseLeftBoundary(startAncestor, how);
7066 			if (frag)
7067 				frag.appendChild(n);
7068 
7069 			commonParent = startAncestor.parentNode;
7070 			startOffset = nodeIndex(startAncestor);
7071 			endOffset = nodeIndex(endAncestor);
7072 			++startOffset;
7073 
7074 			cnt = endOffset - startOffset;
7075 			sibling = startAncestor.nextSibling;
7076 
7077 			while (cnt > 0) {
7078 				nextSibling = sibling.nextSibling;
7079 				n = _traverseFullySelected(sibling, how);
7080 
7081 				if (frag)
7082 					frag.appendChild(n);
7083 
7084 				sibling = nextSibling;
7085 				--cnt;
7086 			}
7087 
7088 			n = _traverseRightBoundary(endAncestor, how);
7089 
7090 			if (frag)
7091 				frag.appendChild(n);
7092 
7093 			if (how != CLONE) {
7094 				t.setStartAfter(startAncestor);
7095 				t.collapse(TRUE);
7096 			}
7097 
7098 			return frag;
7099 		};
7100 
7101 		function _traverseRightBoundary(root, how) {
7102 			var next = _getSelectedNode(t[END_CONTAINER], t[END_OFFSET] - 1), parent, clonedParent, prevSibling, clonedChild, clonedGrandParent, isFullySelected = next != t[END_CONTAINER];
7103 
7104 			if (next == root)
7105 				return _traverseNode(next, isFullySelected, FALSE, how);
7106 
7107 			parent = next.parentNode;
7108 			clonedParent = _traverseNode(parent, FALSE, FALSE, how);
7109 
7110 			while (parent) {
7111 				while (next) {
7112 					prevSibling = next.previousSibling;
7113 					clonedChild = _traverseNode(next, isFullySelected, FALSE, how);
7114 
7115 					if (how != DELETE)
7116 						clonedParent.insertBefore(clonedChild, clonedParent.firstChild);
7117 
7118 					isFullySelected = TRUE;
7119 					next = prevSibling;
7120 				}
7121 
7122 				if (parent == root)
7123 					return clonedParent;
7124 
7125 				next = parent.previousSibling;
7126 				parent = parent.parentNode;
7127 
7128 				clonedGrandParent = _traverseNode(parent, FALSE, FALSE, how);
7129 
7130 				if (how != DELETE)
7131 					clonedGrandParent.appendChild(clonedParent);
7132 
7133 				clonedParent = clonedGrandParent;
7134 			}
7135 		};
7136 
7137 		function _traverseLeftBoundary(root, how) {
7138 			var next = _getSelectedNode(t[START_CONTAINER], t[START_OFFSET]), isFullySelected = next != t[START_CONTAINER], parent, clonedParent, nextSibling, clonedChild, clonedGrandParent;
7139 
7140 			if (next == root)
7141 				return _traverseNode(next, isFullySelected, TRUE, how);
7142 
7143 			parent = next.parentNode;
7144 			clonedParent = _traverseNode(parent, FALSE, TRUE, how);
7145 
7146 			while (parent) {
7147 				while (next) {
7148 					nextSibling = next.nextSibling;
7149 					clonedChild = _traverseNode(next, isFullySelected, TRUE, how);
7150 
7151 					if (how != DELETE)
7152 						clonedParent.appendChild(clonedChild);
7153 
7154 					isFullySelected = TRUE;
7155 					next = nextSibling;
7156 				}
7157 
7158 				if (parent == root)
7159 					return clonedParent;
7160 
7161 				next = parent.nextSibling;
7162 				parent = parent.parentNode;
7163 
7164 				clonedGrandParent = _traverseNode(parent, FALSE, TRUE, how);
7165 
7166 				if (how != DELETE)
7167 					clonedGrandParent.appendChild(clonedParent);
7168 
7169 				clonedParent = clonedGrandParent;
7170 			}
7171 		};
7172 
7173 		function _traverseNode(n, isFullySelected, isLeft, how) {
7174 			var txtValue, newNodeValue, oldNodeValue, offset, newNode;
7175 
7176 			if (isFullySelected)
7177 				return _traverseFullySelected(n, how);
7178 
7179 			if (n.nodeType == 3 /* TEXT_NODE */) {
7180 				txtValue = n.nodeValue;
7181 
7182 				if (isLeft) {
7183 					offset = t[START_OFFSET];
7184 					newNodeValue = txtValue.substring(offset);
7185 					oldNodeValue = txtValue.substring(0, offset);
7186 				} else {
7187 					offset = t[END_OFFSET];
7188 					newNodeValue = txtValue.substring(0, offset);
7189 					oldNodeValue = txtValue.substring(offset);
7190 				}
7191 
7192 				if (how != CLONE)
7193 					n.nodeValue = oldNodeValue;
7194 
7195 				if (how == DELETE)
7196 					return;
7197 
7198 				newNode = dom.clone(n, FALSE);
7199 				newNode.nodeValue = newNodeValue;
7200 
7201 				return newNode;
7202 			}
7203 
7204 			if (how == DELETE)
7205 				return;
7206 
7207 			return dom.clone(n, FALSE);
7208 		};
7209 
7210 		function _traverseFullySelected(n, how) {
7211 			if (how != DELETE)
7212 				return how == CLONE ? dom.clone(n, TRUE) : n;
7213 
7214 			n.parentNode.removeChild(n);
7215 		};
7216 
7217 		function toStringIE() {
7218 			return dom.create('body', null, cloneContents()).outerText;
7219 		}
7220 		
7221 		return t;
7222 	};
7223 
7224 	ns.Range = Range;
7225 
7226 	// Older IE versions doesn't let you override toString by it's constructor so we have to stick it in the prototype
7227 	Range.prototype.toString = function() {
7228 		return this.toStringIE();
7229 	};
7230 })(tinymce.dom);
7231 
7232 (function() {
7233 	function Selection(selection) {
7234 		var self = this, dom = selection.dom, TRUE = true, FALSE = false;
7235 
7236 		function getPosition(rng, start) {
7237 			var checkRng, startIndex = 0, endIndex, inside,
7238 				children, child, offset, index, position = -1, parent;
7239 
7240 			// Setup test range, collapse it and get the parent
7241 			checkRng = rng.duplicate();
7242 			checkRng.collapse(start);
7243 			parent = checkRng.parentElement();
7244 
7245 			// Check if the selection is within the right document
7246 			if (parent.ownerDocument !== selection.dom.doc)
7247 				return;
7248 
7249 			// IE will report non editable elements as it's parent so look for an editable one
7250 			while (parent.contentEditable === "false") {
7251 				parent = parent.parentNode;
7252 			}
7253 
7254 			// If parent doesn't have any children then return that we are inside the element
7255 			if (!parent.hasChildNodes()) {
7256 				return {node : parent, inside : 1};
7257 			}
7258 
7259 			// Setup node list and endIndex
7260 			children = parent.children;
7261 			endIndex = children.length - 1;
7262 
7263 			// Perform a binary search for the position
7264 			while (startIndex <= endIndex) {
7265 				index = Math.floor((startIndex + endIndex) / 2);
7266 
7267 				// Move selection to node and compare the ranges
7268 				child = children[index];
7269 				checkRng.moveToElementText(child);
7270 				position = checkRng.compareEndPoints(start ? 'StartToStart' : 'EndToEnd', rng);
7271 
7272 				// Before/after or an exact match
7273 				if (position > 0) {
7274 					endIndex = index - 1;
7275 				} else if (position < 0) {
7276 					startIndex = index + 1;
7277 				} else {
7278 					return {node : child};
7279 				}
7280 			}
7281 
7282 			// Check if child position is before or we didn't find a position
7283 			if (position < 0) {
7284 				// No element child was found use the parent element and the offset inside that
7285 				if (!child) {
7286 					checkRng.moveToElementText(parent);
7287 					checkRng.collapse(true);
7288 					child = parent;
7289 					inside = true;
7290 				} else
7291 					checkRng.collapse(false);
7292 
7293 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7294 				// We need to walk char by char since rng.text or rng.htmlText will trim line endings
7295 				offset = 0;
7296 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7297 					if (checkRng.move('character', 1) === 0 || parent != checkRng.parentElement()) {
7298 						break;
7299 					}
7300 
7301 					offset++;
7302 				}
7303 			} else {
7304 				// Child position is after the selection endpoint
7305 				checkRng.collapse(true);
7306 
7307 				// Walk character by character in text node until we hit the selected range endpoint, hit the end of document or parent isn't the right one
7308 				offset = 0;
7309 				while (checkRng.compareEndPoints(start ? 'StartToStart' : 'StartToEnd', rng) !== 0) {
7310 					if (checkRng.move('character', -1) === 0 || parent != checkRng.parentElement()) {
7311 						break;
7312 					}
7313 
7314 					offset++;
7315 				}
7316 			}
7317 
7318 			return {node : child, position : position, offset : offset, inside : inside};
7319 		};
7320 
7321 		// Returns a W3C DOM compatible range object by using the IE Range API
7322 		function getRange() {
7323 			var ieRange = selection.getRng(), domRange = dom.createRng(), element, collapsed, tmpRange, element2, bookmark, fail;
7324 
7325 			// If selection is outside the current document just return an empty range
7326 			element = ieRange.item ? ieRange.item(0) : ieRange.parentElement();
7327 			if (element.ownerDocument != dom.doc)
7328 				return domRange;
7329 
7330 			collapsed = selection.isCollapsed();
7331 
7332 			// Handle control selection
7333 			if (ieRange.item) {
7334 				domRange.setStart(element.parentNode, dom.nodeIndex(element));
7335 				domRange.setEnd(domRange.startContainer, domRange.startOffset + 1);
7336 
7337 				return domRange;
7338 			}
7339 
7340 			function findEndPoint(start) {
7341 				var endPoint = getPosition(ieRange, start), container, offset, textNodeOffset = 0, sibling, undef, nodeValue;
7342 
7343 				container = endPoint.node;
7344 				offset = endPoint.offset;
7345 
7346 				if (endPoint.inside && !container.hasChildNodes()) {
7347 					domRange[start ? 'setStart' : 'setEnd'](container, 0);
7348 					return;
7349 				}
7350 
7351 				if (offset === undef) {
7352 					domRange[start ? 'setStartBefore' : 'setEndAfter'](container);
7353 					return;
7354 				}
7355 
7356 				if (endPoint.position < 0) {
7357 					sibling = endPoint.inside ? container.firstChild : container.nextSibling;
7358 
7359 					if (!sibling) {
7360 						domRange[start ? 'setStartAfter' : 'setEndAfter'](container);
7361 						return;
7362 					}
7363 
7364 					if (!offset) {
7365 						if (sibling.nodeType == 3)
7366 							domRange[start ? 'setStart' : 'setEnd'](sibling, 0);
7367 						else
7368 							domRange[start ? 'setStartBefore' : 'setEndBefore'](sibling);
7369 
7370 						return;
7371 					}
7372 
7373 					// Find the text node and offset
7374 					while (sibling) {
7375 						nodeValue = sibling.nodeValue;
7376 						textNodeOffset += nodeValue.length;
7377 
7378 						// We are at or passed the position we where looking for
7379 						if (textNodeOffset >= offset) {
7380 							container = sibling;
7381 							textNodeOffset -= offset;
7382 							textNodeOffset = nodeValue.length - textNodeOffset;
7383 							break;
7384 						}
7385 
7386 						sibling = sibling.nextSibling;
7387 					}
7388 				} else {
7389 					// Find the text node and offset
7390 					sibling = container.previousSibling;
7391 
7392 					if (!sibling)
7393 						return domRange[start ? 'setStartBefore' : 'setEndBefore'](container);
7394 
7395 					// If there isn't any text to loop then use the first position
7396 					if (!offset) {
7397 						if (container.nodeType == 3)
7398 							domRange[start ? 'setStart' : 'setEnd'](sibling, container.nodeValue.length);
7399 						else
7400 							domRange[start ? 'setStartAfter' : 'setEndAfter'](sibling);
7401 
7402 						return;
7403 					}
7404 
7405 					while (sibling) {
7406 						textNodeOffset += sibling.nodeValue.length;
7407 
7408 						// We are at or passed the position we where looking for
7409 						if (textNodeOffset >= offset) {
7410 							container = sibling;
7411 							textNodeOffset -= offset;
7412 							break;
7413 						}
7414 
7415 						sibling = sibling.previousSibling;
7416 					}
7417 				}
7418 
7419 				domRange[start ? 'setStart' : 'setEnd'](container, textNodeOffset);
7420 			};
7421 
7422 			try {
7423 				// Find start point
7424 				findEndPoint(true);
7425 
7426 				// Find end point if needed
7427 				if (!collapsed)
7428 					findEndPoint();
7429 			} catch (ex) {
7430 				// IE has a nasty bug where text nodes might throw "invalid argument" when you
7431 				// access the nodeValue or other properties of text nodes. This seems to happend when
7432 				// text nodes are split into two nodes by a delete/backspace call. So lets detect it and try to fix it.
7433 				if (ex.number == -2147024809) {
7434 					// Get the current selection
7435 					bookmark = self.getBookmark(2);
7436 
7437 					// Get start element
7438 					tmpRange = ieRange.duplicate();
7439 					tmpRange.collapse(true);
7440 					element = tmpRange.parentElement();
7441 
7442 					// Get end element
7443 					if (!collapsed) {
7444 						tmpRange = ieRange.duplicate();
7445 						tmpRange.collapse(false);
7446 						element2 = tmpRange.parentElement();
7447 						element2.innerHTML = element2.innerHTML;
7448 					}
7449 
7450 					// Remove the broken elements
7451 					element.innerHTML = element.innerHTML;
7452 
7453 					// Restore the selection
7454 					self.moveToBookmark(bookmark);
7455 
7456 					// Since the range has moved we need to re-get it
7457 					ieRange = selection.getRng();
7458 
7459 					// Find start point
7460 					findEndPoint(true);
7461 
7462 					// Find end point if needed
7463 					if (!collapsed)
7464 						findEndPoint();
7465 				} else
7466 					throw ex; // Throw other errors
7467 			}
7468 
7469 			return domRange;
7470 		};
7471 
7472 		this.getBookmark = function(type) {
7473 			var rng = selection.getRng(), start, end, bookmark = {};
7474 
7475 			function getIndexes(node) {
7476 				var parent, root, children, i, indexes = [];
7477 
7478 				parent = node.parentNode;
7479 				root = dom.getRoot().parentNode;
7480 
7481 				while (parent != root && parent.nodeType !== 9) {
7482 					children = parent.children;
7483 
7484 					i = children.length;
7485 					while (i--) {
7486 						if (node === children[i]) {
7487 							indexes.push(i);
7488 							break;
7489 						}
7490 					}
7491 
7492 					node = parent;
7493 					parent = parent.parentNode;
7494 				}
7495 
7496 				return indexes;
7497 			};
7498 
7499 			function getBookmarkEndPoint(start) {
7500 				var position;
7501 
7502 				position = getPosition(rng, start);
7503 				if (position) {
7504 					return {
7505 						position : position.position,
7506 						offset : position.offset,
7507 						indexes : getIndexes(position.node),
7508 						inside : position.inside
7509 					};
7510 				}
7511 			};
7512 
7513 			// Non ubstructive bookmark
7514 			if (type === 2) {
7515 				// Handle text selection
7516 				if (!rng.item) {
7517 					bookmark.start = getBookmarkEndPoint(true);
7518 
7519 					if (!selection.isCollapsed())
7520 						bookmark.end = getBookmarkEndPoint();
7521 				} else
7522 					bookmark.start = {ctrl : true, indexes : getIndexes(rng.item(0))};
7523 			}
7524 
7525 			return bookmark;
7526 		};
7527 
7528 		this.moveToBookmark = function(bookmark) {
7529 			var rng, body = dom.doc.body;
7530 
7531 			function resolveIndexes(indexes) {
7532 				var node, i, idx, children;
7533 
7534 				node = dom.getRoot();
7535 				for (i = indexes.length - 1; i >= 0; i--) {
7536 					children = node.children;
7537 					idx = indexes[i];
7538 
7539 					if (idx <= children.length - 1) {
7540 						node = children[idx];
7541 					}
7542 				}
7543 
7544 				return node;
7545 			};
7546 			
7547 			function setBookmarkEndPoint(start) {
7548 				var endPoint = bookmark[start ? 'start' : 'end'], moveLeft, moveRng, undef;
7549 
7550 				if (endPoint) {
7551 					moveLeft = endPoint.position > 0;
7552 
7553 					moveRng = body.createTextRange();
7554 					moveRng.moveToElementText(resolveIndexes(endPoint.indexes));
7555 
7556 					offset = endPoint.offset;
7557 					if (offset !== undef) {
7558 						moveRng.collapse(endPoint.inside || moveLeft);
7559 						moveRng.moveStart('character', moveLeft ? -offset : offset);
7560 					} else
7561 						moveRng.collapse(start);
7562 
7563 					rng.setEndPoint(start ? 'StartToStart' : 'EndToStart', moveRng);
7564 
7565 					if (start)
7566 						rng.collapse(true);
7567 				}
7568 			};
7569 
7570 			if (bookmark.start) {
7571 				if (bookmark.start.ctrl) {
7572 					rng = body.createControlRange();
7573 					rng.addElement(resolveIndexes(bookmark.start.indexes));
7574 					rng.select();
7575 				} else {
7576 					rng = body.createTextRange();
7577 					setBookmarkEndPoint(true);
7578 					setBookmarkEndPoint();
7579 					rng.select();
7580 				}
7581 			}
7582 		};
7583 
7584 		this.addRange = function(rng) {
7585 			var ieRng, ctrlRng, startContainer, startOffset, endContainer, endOffset, sibling, doc = selection.dom.doc, body = doc.body;
7586 
7587 			function setEndPoint(start) {
7588 				var container, offset, marker, tmpRng, nodes;
7589 
7590 				marker = dom.create('a');
7591 				container = start ? startContainer : endContainer;
7592 				offset = start ? startOffset : endOffset;
7593 				tmpRng = ieRng.duplicate();
7594 
7595 				if (container == doc || container == doc.documentElement) {
7596 					container = body;
7597 					offset = 0;
7598 				}
7599 
7600 				if (container.nodeType == 3) {
7601 					container.parentNode.insertBefore(marker, container);
7602 					tmpRng.moveToElementText(marker);
7603 					tmpRng.moveStart('character', offset);
7604 					dom.remove(marker);
7605 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7606 				} else {
7607 					nodes = container.childNodes;
7608 
7609 					if (nodes.length) {
7610 						if (offset >= nodes.length) {
7611 							dom.insertAfter(marker, nodes[nodes.length - 1]);
7612 						} else {
7613 							container.insertBefore(marker, nodes[offset]);
7614 						}
7615 
7616 						tmpRng.moveToElementText(marker);
7617 					} else if (container.canHaveHTML) {
7618 						// Empty node selection for example <div>|</div>
7619 						// Setting innerHTML with a span marker then remove that marker seems to keep empty block elements open
7620 						container.innerHTML = '<span>\uFEFF</span>';
7621 						marker = container.firstChild;
7622 						tmpRng.moveToElementText(marker);
7623 						tmpRng.collapse(FALSE); // Collapse false works better than true for some odd reason
7624 					}
7625 
7626 					ieRng.setEndPoint(start ? 'StartToStart' : 'EndToEnd', tmpRng);
7627 					dom.remove(marker);
7628 				}
7629 			}
7630 
7631 			// Setup some shorter versions
7632 			startContainer = rng.startContainer;
7633 			startOffset = rng.startOffset;
7634 			endContainer = rng.endContainer;
7635 			endOffset = rng.endOffset;
7636 			ieRng = body.createTextRange();
7637 
7638 			// If single element selection then try making a control selection out of it
7639 			if (startContainer == endContainer && startContainer.nodeType == 1) {
7640 				// Trick to place the caret inside an empty block element like <p></p>
7641 				if (startOffset == endOffset && !startContainer.hasChildNodes()) {
7642 					if (startContainer.canHaveHTML) {
7643 						// Check if previous sibling is an empty block if it is then we need to render it
7644 						// IE would otherwise move the caret into the sibling instead of the empty startContainer see: #5236
7645 						// Example this: <p></p><p>|</p> would become this: <p>|</p><p></p>
7646 						sibling = startContainer.previousSibling;
7647 						if (sibling && !sibling.hasChildNodes() && dom.isBlock(sibling)) {
7648 							sibling.innerHTML = '\uFEFF';
7649 						} else {
7650 							sibling = null;
7651 						}
7652 
7653 						startContainer.innerHTML = '<span>\uFEFF</span><span>\uFEFF</span>';
7654 						ieRng.moveToElementText(startContainer.lastChild);
7655 						ieRng.select();
7656 						dom.doc.selection.clear();
7657 						startContainer.innerHTML = '';
7658 
7659 						if (sibling) {
7660 							sibling.innerHTML = '';
7661 						}
7662 						return;
7663 					} else {
7664 						startOffset = dom.nodeIndex(startContainer);
7665 						startContainer = startContainer.parentNode;
7666 					}
7667 				}
7668 
7669 				if (startOffset == endOffset - 1) {
7670 					try {
7671 						ctrlRng = body.createControlRange();
7672 						ctrlRng.addElement(startContainer.childNodes[startOffset]);
7673 						ctrlRng.select();
7674 						return;
7675 					} catch (ex) {
7676 						// Ignore
7677 					}
7678 				}
7679 			}
7680 
7681 			// Set start/end point of selection
7682 			setEndPoint(true);
7683 			setEndPoint();
7684 
7685 			// Select the new range and scroll it into view
7686 			ieRng.select();
7687 		};
7688 
7689 		// Expose range method
7690 		this.getRangeAt = getRange;
7691 	};
7692 
7693 	// Expose the selection object
7694 	tinymce.dom.TridentSelection = Selection;
7695 })();
7696 
7697 
7698 (function(tinymce) {
7699 	tinymce.dom.Element = function(id, settings) {
7700 		var t = this, dom, el;
7701 
7702 		t.settings = settings = settings || {};
7703 		t.id = id;
7704 		t.dom = dom = settings.dom || tinymce.DOM;
7705 
7706 		// Only IE leaks DOM references, this is a lot faster
7707 		if (!tinymce.isIE)
7708 			el = dom.get(t.id);
7709 
7710 		tinymce.each(
7711 				('getPos,getRect,getParent,add,setStyle,getStyle,setStyles,' + 
7712 				'setAttrib,setAttribs,getAttrib,addClass,removeClass,' + 
7713 				'hasClass,getOuterHTML,setOuterHTML,remove,show,hide,' + 
7714 				'isHidden,setHTML,get').split(/,/), function(k) {
7715 					t[k] = function() {
7716 						var a = [id], i;
7717 
7718 						for (i = 0; i < arguments.length; i++)
7719 							a.push(arguments[i]);
7720 
7721 						a = dom[k].apply(dom, a);
7722 						t.update(k);
7723 
7724 						return a;
7725 					};
7726 			}
7727 		);
7728 
7729 		tinymce.extend(t, {
7730 			on : function(n, f, s) {
7731 				return tinymce.dom.Event.add(t.id, n, f, s);
7732 			},
7733 
7734 			getXY : function() {
7735 				return {
7736 					x : parseInt(t.getStyle('left')),
7737 					y : parseInt(t.getStyle('top'))
7738 				};
7739 			},
7740 
7741 			getSize : function() {
7742 				var n = dom.get(t.id);
7743 
7744 				return {
7745 					w : parseInt(t.getStyle('width') || n.clientWidth),
7746 					h : parseInt(t.getStyle('height') || n.clientHeight)
7747 				};
7748 			},
7749 
7750 			moveTo : function(x, y) {
7751 				t.setStyles({left : x, top : y});
7752 			},
7753 
7754 			moveBy : function(x, y) {
7755 				var p = t.getXY();
7756 
7757 				t.moveTo(p.x + x, p.y + y);
7758 			},
7759 
7760 			resizeTo : function(w, h) {
7761 				t.setStyles({width : w, height : h});
7762 			},
7763 
7764 			resizeBy : function(w, h) {
7765 				var s = t.getSize();
7766 
7767 				t.resizeTo(s.w + w, s.h + h);
7768 			},
7769 
7770 			update : function(k) {
7771 				var b;
7772 
7773 				if (tinymce.isIE6 && settings.blocker) {
7774 					k = k || '';
7775 
7776 					// Ignore getters
7777 					if (k.indexOf('get') === 0 || k.indexOf('has') === 0 || k.indexOf('is') === 0)
7778 						return;
7779 
7780 					// Remove blocker on remove
7781 					if (k == 'remove') {
7782 						dom.remove(t.blocker);
7783 						return;
7784 					}
7785 
7786 					if (!t.blocker) {
7787 						t.blocker = dom.uniqueId();
7788 						b = dom.add(settings.container || dom.getRoot(), 'iframe', {id : t.blocker, style : 'position:absolute;', frameBorder : 0, src : 'javascript:""'});
7789 						dom.setStyle(b, 'opacity', 0);
7790 					} else
7791 						b = dom.get(t.blocker);
7792 
7793 					dom.setStyles(b, {
7794 						left : t.getStyle('left', 1),
7795 						top : t.getStyle('top', 1),
7796 						width : t.getStyle('width', 1),
7797 						height : t.getStyle('height', 1),
7798 						display : t.getStyle('display', 1),
7799 						zIndex : parseInt(t.getStyle('zIndex', 1) || 0) - 1
7800 					});
7801 				}
7802 			}
7803 		});
7804 	};
7805 })(tinymce);
7806 
7807 (function(tinymce) {
7808 	function trimNl(s) {
7809 		return s.replace(/[\n\r]+/g, '');
7810 	};
7811 
7812 	// Shorten names
7813 	var is = tinymce.is, isIE = tinymce.isIE, each = tinymce.each, TreeWalker = tinymce.dom.TreeWalker;
7814 
7815 	tinymce.create('tinymce.dom.Selection', {
7816 		Selection : function(dom, win, serializer, editor) {
7817 			var t = this;
7818 
7819 			t.dom = dom;
7820 			t.win = win;
7821 			t.serializer = serializer;
7822 			t.editor = editor;
7823 
7824 			// Add events
7825 			each([
7826 				'onBeforeSetContent',
7827 
7828 				'onBeforeGetContent',
7829 
7830 				'onSetContent',
7831 
7832 				'onGetContent'
7833 			], function(e) {
7834 				t[e] = new tinymce.util.Dispatcher(t);
7835 			});
7836 
7837 			// No W3C Range support
7838 			if (!t.win.getSelection)
7839 				t.tridentSel = new tinymce.dom.TridentSelection(t);
7840 
7841 			if (tinymce.isIE && dom.boxModel)
7842 				this._fixIESelection();
7843 
7844 			// Prevent leaks
7845 			tinymce.addUnload(t.destroy, t);
7846 		},
7847 
7848 		setCursorLocation: function(node, offset) {
7849 			var t = this; var r = t.dom.createRng();
7850 			r.setStart(node, offset);
7851 			r.setEnd(node, offset);
7852 			t.setRng(r);
7853 			t.collapse(false);
7854 		},
7855 		getContent : function(s) {
7856 			var t = this, r = t.getRng(), e = t.dom.create("body"), se = t.getSel(), wb, wa, n;
7857 
7858 			s = s || {};
7859 			wb = wa = '';
7860 			s.get = true;
7861 			s.format = s.format || 'html';
7862 			s.forced_root_block = '';
7863 			t.onBeforeGetContent.dispatch(t, s);
7864 
7865 			if (s.format == 'text')
7866 				return t.isCollapsed() ? '' : (r.text || (se.toString ? se.toString() : ''));
7867 
7868 			if (r.cloneContents) {
7869 				n = r.cloneContents();
7870 
7871 				if (n)
7872 					e.appendChild(n);
7873 			} else if (is(r.item) || is(r.htmlText)) {
7874 				// IE will produce invalid markup if elements are present that
7875 				// it doesn't understand like custom elements or HTML5 elements.
7876 				// Adding a BR in front of the contents and then remoiving it seems to fix it though.
7877 				e.innerHTML = '<br>' + (r.item ? r.item(0).outerHTML : r.htmlText);
7878 				e.removeChild(e.firstChild);
7879 			} else
7880 				e.innerHTML = r.toString();
7881 
7882 			// Keep whitespace before and after
7883 			if (/^\s/.test(e.innerHTML))
7884 				wb = ' ';
7885 
7886 			if (/\s+$/.test(e.innerHTML))
7887 				wa = ' ';
7888 
7889 			s.getInner = true;
7890 
7891 			s.content = t.isCollapsed() ? '' : wb + t.serializer.serialize(e, s) + wa;
7892 			t.onGetContent.dispatch(t, s);
7893 
7894 			return s.content;
7895 		},
7896 
7897 		setContent : function(content, args) {
7898 			var self = this, rng = self.getRng(), caretNode, doc = self.win.document, frag, temp;
7899 
7900 			args = args || {format : 'html'};
7901 			args.set = true;
7902 			content = args.content = content;
7903 
7904 			// Dispatch before set content event
7905 			if (!args.no_events)
7906 				self.onBeforeSetContent.dispatch(self, args);
7907 
7908 			content = args.content;
7909 
7910 			if (rng.insertNode) {
7911 				// Make caret marker since insertNode places the caret in the beginning of text after insert
7912 				content += '<span id="__caret">_</span>';
7913 
7914 				// Delete and insert new node
7915 				if (rng.startContainer == doc && rng.endContainer == doc) {
7916 					// WebKit will fail if the body is empty since the range is then invalid and it can't insert contents
7917 					doc.body.innerHTML = content;
7918 				} else {
7919 					rng.deleteContents();
7920 
7921 					if (doc.body.childNodes.length === 0) {
7922 						doc.body.innerHTML = content;
7923 					} else {
7924 						// createContextualFragment doesn't exists in IE 9 DOMRanges
7925 						if (rng.createContextualFragment) {
7926 							rng.insertNode(rng.createContextualFragment(content));
7927 						} else {
7928 							// Fake createContextualFragment call in IE 9
7929 							frag = doc.createDocumentFragment();
7930 							temp = doc.createElement('div');
7931 
7932 							frag.appendChild(temp);
7933 							temp.outerHTML = content;
7934 
7935 							rng.insertNode(frag);
7936 						}
7937 					}
7938 				}
7939 
7940 				// Move to caret marker
7941 				caretNode = self.dom.get('__caret');
7942 
7943 				// Make sure we wrap it compleatly, Opera fails with a simple select call
7944 				rng = doc.createRange();
7945 				rng.setStartBefore(caretNode);
7946 				rng.setEndBefore(caretNode);
7947 				self.setRng(rng);
7948 
7949 				// Remove the caret position
7950 				self.dom.remove('__caret');
7951 
7952 				try {
7953 					self.setRng(rng);
7954 				} catch (ex) {
7955 					// Might fail on Opera for some odd reason
7956 				}
7957 			} else {
7958 				if (rng.item) {
7959 					// Delete content and get caret text selection
7960 					doc.execCommand('Delete', false, null);
7961 					rng = self.getRng();
7962 				}
7963 
7964 				// Explorer removes spaces from the beginning of pasted contents
7965 				if (/^\s+/.test(content)) {
7966 					rng.pasteHTML('<span id="__mce_tmp">_</span>' + content);
7967 					self.dom.remove('__mce_tmp');
7968 				} else
7969 					rng.pasteHTML(content);
7970 			}
7971 
7972 			// Dispatch set content event
7973 			if (!args.no_events)
7974 				self.onSetContent.dispatch(self, args);
7975 		},
7976 
7977 		getStart : function() {
7978 			var self = this, rng = self.getRng(), startElement, parentElement, checkRng, node;
7979 
7980 			if (rng.duplicate || rng.item) {
7981 				// Control selection, return first item
7982 				if (rng.item)
7983 					return rng.item(0);
7984 
7985 				// Get start element
7986 				checkRng = rng.duplicate();
7987 				checkRng.collapse(1);
7988 				startElement = checkRng.parentElement();
7989 				if (startElement.ownerDocument !== self.dom.doc) {
7990 					startElement = self.dom.getRoot();
7991 				}
7992 
7993 				// Check if range parent is inside the start element, then return the inner parent element
7994 				// This will fix issues when a single element is selected, IE would otherwise return the wrong start element
7995 				parentElement = node = rng.parentElement();
7996 				while (node = node.parentNode) {
7997 					if (node == startElement) {
7998 						startElement = parentElement;
7999 						break;
8000 					}
8001 				}
8002 
8003 				return startElement;
8004 			} else {
8005 				startElement = rng.startContainer;
8006 
8007 				if (startElement.nodeType == 1 && startElement.hasChildNodes())
8008 					startElement = startElement.childNodes[Math.min(startElement.childNodes.length - 1, rng.startOffset)];
8009 
8010 				if (startElement && startElement.nodeType == 3)
8011 					return startElement.parentNode;
8012 
8013 				return startElement;
8014 			}
8015 		},
8016 
8017 		getEnd : function() {
8018 			var self = this, rng = self.getRng(), endElement, endOffset;
8019 
8020 			if (rng.duplicate || rng.item) {
8021 				if (rng.item)
8022 					return rng.item(0);
8023 
8024 				rng = rng.duplicate();
8025 				rng.collapse(0);
8026 				endElement = rng.parentElement();
8027 				if (endElement.ownerDocument !== self.dom.doc) {
8028 					endElement = self.dom.getRoot();
8029 				}
8030 
8031 				if (endElement && endElement.nodeName == 'BODY')
8032 					return endElement.lastChild || endElement;
8033 
8034 				return endElement;
8035 			} else {
8036 				endElement = rng.endContainer;
8037 				endOffset = rng.endOffset;
8038 
8039 				if (endElement.nodeType == 1 && endElement.hasChildNodes())
8040 					endElement = endElement.childNodes[endOffset > 0 ? endOffset - 1 : endOffset];
8041 
8042 				if (endElement && endElement.nodeType == 3)
8043 					return endElement.parentNode;
8044 
8045 				return endElement;
8046 			}
8047 		},
8048 
8049 		getBookmark : function(type, normalized) {
8050 			var t = this, dom = t.dom, rng, rng2, id, collapsed, name, element, index, chr = '\uFEFF', styles;
8051 
8052 			function findIndex(name, element) {
8053 				var index = 0;
8054 
8055 				each(dom.select(name), function(node, i) {
8056 					if (node == element)
8057 						index = i;
8058 				});
8059 
8060 				return index;
8061 			};
8062 
8063 			function normalizeTableCellSelection(rng) {
8064 				function moveEndPoint(start) {
8065 					var container, offset, childNodes, prefix = start ? 'start' : 'end';
8066 
8067 					container = rng[prefix + 'Container'];
8068 					offset = rng[prefix + 'Offset'];
8069 
8070 					if (container.nodeType == 1 && container.nodeName == "TR") {
8071 						childNodes = container.childNodes;
8072 						container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
8073 						if (container) {
8074 							offset = start ? 0 : container.childNodes.length;
8075 							rng['set' + (start ? 'Start' : 'End')](container, offset);
8076 						}
8077 					}
8078 				};
8079 
8080 				moveEndPoint(true);
8081 				moveEndPoint();
8082 
8083 				return rng;
8084 			};
8085 
8086 			function getLocation() {
8087 				var rng = t.getRng(true), root = dom.getRoot(), bookmark = {};
8088 
8089 				function getPoint(rng, start) {
8090 					var container = rng[start ? 'startContainer' : 'endContainer'],
8091 						offset = rng[start ? 'startOffset' : 'endOffset'], point = [], node, childNodes, after = 0;
8092 
8093 					if (container.nodeType == 3) {
8094 						if (normalized) {
8095 							for (node = container.previousSibling; node && node.nodeType == 3; node = node.previousSibling)
8096 								offset += node.nodeValue.length;
8097 						}
8098 
8099 						point.push(offset);
8100 					} else {
8101 						childNodes = container.childNodes;
8102 
8103 						if (offset >= childNodes.length && childNodes.length) {
8104 							after = 1;
8105 							offset = Math.max(0, childNodes.length - 1);
8106 						}
8107 
8108 						point.push(t.dom.nodeIndex(childNodes[offset], normalized) + after);
8109 					}
8110 
8111 					for (; container && container != root; container = container.parentNode)
8112 						point.push(t.dom.nodeIndex(container, normalized));
8113 
8114 					return point;
8115 				};
8116 
8117 				bookmark.start = getPoint(rng, true);
8118 
8119 				if (!t.isCollapsed())
8120 					bookmark.end = getPoint(rng);
8121 
8122 				return bookmark;
8123 			};
8124 
8125 			if (type == 2) {
8126 				if (t.tridentSel)
8127 					return t.tridentSel.getBookmark(type);
8128 
8129 				return getLocation();
8130 			}
8131 
8132 			// Handle simple range
8133 			if (type)
8134 				return {rng : t.getRng()};
8135 
8136 			rng = t.getRng();
8137 			id = dom.uniqueId();
8138 			collapsed = tinyMCE.activeEditor.selection.isCollapsed();
8139 			styles = 'overflow:hidden;line-height:0px';
8140 
8141 			// Explorer method
8142 			if (rng.duplicate || rng.item) {
8143 				// Text selection
8144 				if (!rng.item) {
8145 					rng2 = rng.duplicate();
8146 
8147 					try {
8148 						// Insert start marker
8149 						rng.collapse();
8150 						rng.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_start" style="' + styles + '">' + chr + '</span>');
8151 
8152 						// Insert end marker
8153 						if (!collapsed) {
8154 							rng2.collapse(false);
8155 
8156 							// Detect the empty space after block elements in IE and move the end back one character <p></p>] becomes <p>]</p>
8157 							rng.moveToElementText(rng2.parentElement());
8158 							if (rng.compareEndPoints('StartToEnd', rng2) === 0)
8159 								rng2.move('character', -1);
8160 
8161 							rng2.pasteHTML('<span data-mce-type="bookmark" id="' + id + '_end" style="' + styles + '">' + chr + '</span>');
8162 						}
8163 					} catch (ex) {
8164 						// IE might throw unspecified error so lets ignore it
8165 						return null;
8166 					}
8167 				} else {
8168 					// Control selection
8169 					element = rng.item(0);
8170 					name = element.nodeName;
8171 
8172 					return {name : name, index : findIndex(name, element)};
8173 				}
8174 			} else {
8175 				element = t.getNode();
8176 				name = element.nodeName;
8177 				if (name == 'IMG')
8178 					return {name : name, index : findIndex(name, element)};
8179 
8180 				// W3C method
8181 				rng2 = normalizeTableCellSelection(rng.cloneRange());
8182 
8183 				// Insert end marker
8184 				if (!collapsed) {
8185 					rng2.collapse(false);
8186 					rng2.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_end', style : styles}, chr));
8187 				}
8188 
8189 				rng = normalizeTableCellSelection(rng);
8190 				rng.collapse(true);
8191 				rng.insertNode(dom.create('span', {'data-mce-type' : "bookmark", id : id + '_start', style : styles}, chr));
8192 			}
8193 
8194 			t.moveToBookmark({id : id, keep : 1});
8195 
8196 			return {id : id};
8197 		},
8198 
8199 		moveToBookmark : function(bookmark) {
8200 			var t = this, dom = t.dom, marker1, marker2, rng, root, startContainer, endContainer, startOffset, endOffset;
8201 
8202 			function setEndPoint(start) {
8203 				var point = bookmark[start ? 'start' : 'end'], i, node, offset, children;
8204 
8205 				if (point) {
8206 					offset = point[0];
8207 
8208 					// Find container node
8209 					for (node = root, i = point.length - 1; i >= 1; i--) {
8210 						children = node.childNodes;
8211 
8212 						if (point[i] > children.length - 1)
8213 							return;
8214 
8215 						node = children[point[i]];
8216 					}
8217 
8218 					// Move text offset to best suitable location
8219 					if (node.nodeType === 3)
8220 						offset = Math.min(point[0], node.nodeValue.length);
8221 
8222 					// Move element offset to best suitable location
8223 					if (node.nodeType === 1)
8224 						offset = Math.min(point[0], node.childNodes.length);
8225 
8226 					// Set offset within container node
8227 					if (start)
8228 						rng.setStart(node, offset);
8229 					else
8230 						rng.setEnd(node, offset);
8231 				}
8232 
8233 				return true;
8234 			};
8235 
8236 			function restoreEndPoint(suffix) {
8237 				var marker = dom.get(bookmark.id + '_' + suffix), node, idx, next, prev, keep = bookmark.keep;
8238 
8239 				if (marker) {
8240 					node = marker.parentNode;
8241 
8242 					if (suffix == 'start') {
8243 						if (!keep) {
8244 							idx = dom.nodeIndex(marker);
8245 						} else {
8246 							node = marker.firstChild;
8247 							idx = 1;
8248 						}
8249 
8250 						startContainer = endContainer = node;
8251 						startOffset = endOffset = idx;
8252 					} else {
8253 						if (!keep) {
8254 							idx = dom.nodeIndex(marker);
8255 						} else {
8256 							node = marker.firstChild;
8257 							idx = 1;
8258 						}
8259 
8260 						endContainer = node;
8261 						endOffset = idx;
8262 					}
8263 
8264 					if (!keep) {
8265 						prev = marker.previousSibling;
8266 						next = marker.nextSibling;
8267 
8268 						// Remove all marker text nodes
8269 						each(tinymce.grep(marker.childNodes), function(node) {
8270 							if (node.nodeType == 3)
8271 								node.nodeValue = node.nodeValue.replace(/\uFEFF/g, '');
8272 						});
8273 
8274 						// Remove marker but keep children if for example contents where inserted into the marker
8275 						// Also remove duplicated instances of the marker for example by a split operation or by WebKit auto split on paste feature
8276 						while (marker = dom.get(bookmark.id + '_' + suffix))
8277 							dom.remove(marker, 1);
8278 
8279 						// If siblings are text nodes then merge them unless it's Opera since it some how removes the node
8280 						// and we are sniffing since adding a lot of detection code for a browser with 3% of the market isn't worth the effort. Sorry, Opera but it's just a fact
8281 						if (prev && next && prev.nodeType == next.nodeType && prev.nodeType == 3 && !tinymce.isOpera) {
8282 							idx = prev.nodeValue.length;
8283 							prev.appendData(next.nodeValue);
8284 							dom.remove(next);
8285 
8286 							if (suffix == 'start') {
8287 								startContainer = endContainer = prev;
8288 								startOffset = endOffset = idx;
8289 							} else {
8290 								endContainer = prev;
8291 								endOffset = idx;
8292 							}
8293 						}
8294 					}
8295 				}
8296 			};
8297 
8298 			function addBogus(node) {
8299 				// Adds a bogus BR element for empty block elements
8300 				if (dom.isBlock(node) && !node.innerHTML && !isIE)
8301 					node.innerHTML = '<br data-mce-bogus="1" />';
8302 
8303 				return node;
8304 			};
8305 
8306 			if (bookmark) {
8307 				if (bookmark.start) {
8308 					rng = dom.createRng();
8309 					root = dom.getRoot();
8310 
8311 					if (t.tridentSel)
8312 						return t.tridentSel.moveToBookmark(bookmark);
8313 
8314 					if (setEndPoint(true) && setEndPoint()) {
8315 						t.setRng(rng);
8316 					}
8317 				} else if (bookmark.id) {
8318 					// Restore start/end points
8319 					restoreEndPoint('start');
8320 					restoreEndPoint('end');
8321 
8322 					if (startContainer) {
8323 						rng = dom.createRng();
8324 						rng.setStart(addBogus(startContainer), startOffset);
8325 						rng.setEnd(addBogus(endContainer), endOffset);
8326 						t.setRng(rng);
8327 					}
8328 				} else if (bookmark.name) {
8329 					t.select(dom.select(bookmark.name)[bookmark.index]);
8330 				} else if (bookmark.rng)
8331 					t.setRng(bookmark.rng);
8332 			}
8333 		},
8334 
8335 		select : function(node, content) {
8336 			var t = this, dom = t.dom, rng = dom.createRng(), idx;
8337 
8338 			function setPoint(node, start) {
8339 				var walker = new TreeWalker(node, node);
8340 
8341 				do {
8342 					// Text node
8343 					if (node.nodeType == 3 && tinymce.trim(node.nodeValue).length !== 0) {
8344 						if (start)
8345 							rng.setStart(node, 0);
8346 						else
8347 							rng.setEnd(node, node.nodeValue.length);
8348 
8349 						return;
8350 					}
8351 
8352 					// BR element
8353 					if (node.nodeName == 'BR') {
8354 						if (start)
8355 							rng.setStartBefore(node);
8356 						else
8357 							rng.setEndBefore(node);
8358 
8359 						return;
8360 					}
8361 				} while (node = (start ? walker.next() : walker.prev()));
8362 			};
8363 
8364 			if (node) {
8365 				idx = dom.nodeIndex(node);
8366 				rng.setStart(node.parentNode, idx);
8367 				rng.setEnd(node.parentNode, idx + 1);
8368 
8369 				// Find first/last text node or BR element
8370 				if (content) {
8371 					setPoint(node, 1);
8372 					setPoint(node);
8373 				}
8374 
8375 				t.setRng(rng);
8376 			}
8377 
8378 			return node;
8379 		},
8380 
8381 		isCollapsed : function() {
8382 			var t = this, r = t.getRng(), s = t.getSel();
8383 
8384 			if (!r || r.item)
8385 				return false;
8386 
8387 			if (r.compareEndPoints)
8388 				return r.compareEndPoints('StartToEnd', r) === 0;
8389 
8390 			return !s || r.collapsed;
8391 		},
8392 
8393 		collapse : function(to_start) {
8394 			var self = this, rng = self.getRng(), node;
8395 
8396 			// Control range on IE
8397 			if (rng.item) {
8398 				node = rng.item(0);
8399 				rng = self.win.document.body.createTextRange();
8400 				rng.moveToElementText(node);
8401 			}
8402 
8403 			rng.collapse(!!to_start);
8404 			self.setRng(rng);
8405 		},
8406 
8407 		getSel : function() {
8408 			var t = this, w = this.win;
8409 
8410 			return w.getSelection ? w.getSelection() : w.document.selection;
8411 		},
8412 
8413 		getRng : function(w3c) {
8414 			var self = this, selection, rng, elm, doc = self.win.document;
8415 
8416 			// Found tridentSel object then we need to use that one
8417 			if (w3c && self.tridentSel) {
8418 				return self.tridentSel.getRangeAt(0);
8419 			}
8420 
8421 			try {
8422 				if (selection = self.getSel()) {
8423 					rng = selection.rangeCount > 0 ? selection.getRangeAt(0) : (selection.createRange ? selection.createRange() : doc.createRange());
8424 				}
8425 			} catch (ex) {
8426 				// IE throws unspecified error here if TinyMCE is placed in a frame/iframe
8427 			}
8428 
8429 			// We have W3C ranges and it's IE then fake control selection since IE9 doesn't handle that correctly yet
8430 			if (tinymce.isIE && rng && rng.setStart && doc.selection.createRange().item) {
8431 				elm = doc.selection.createRange().item(0);
8432 				rng = doc.createRange();
8433 				rng.setStartBefore(elm);
8434 				rng.setEndAfter(elm);
8435 			}
8436 
8437 			// No range found then create an empty one
8438 			// This can occur when the editor is placed in a hidden container element on Gecko
8439 			// Or on IE when there was an exception
8440 			if (!rng) {
8441 				rng = doc.createRange ? doc.createRange() : doc.body.createTextRange();
8442 			}
8443 
8444 			// If range is at start of document then move it to start of body
8445 			if (rng.setStart && rng.startContainer.nodeType === 9 && rng.collapsed) {
8446 				elm = self.dom.getRoot();
8447 				rng.setStart(elm, 0);
8448 				rng.setEnd(elm, 0);
8449 			}
8450 
8451 			if (self.selectedRange && self.explicitRange) {
8452 				if (rng.compareBoundaryPoints(rng.START_TO_START, self.selectedRange) === 0 && rng.compareBoundaryPoints(rng.END_TO_END, self.selectedRange) === 0) {
8453 					// Safari, Opera and Chrome only ever select text which causes the range to change.
8454 					// This lets us use the originally set range if the selection hasn't been changed by the user.
8455 					rng = self.explicitRange;
8456 				} else {
8457 					self.selectedRange = null;
8458 					self.explicitRange = null;
8459 				}
8460 			}
8461 
8462 			return rng;
8463 		},
8464 
8465 		setRng : function(r, forward) {
8466 			var s, t = this;
8467 
8468 			if (!t.tridentSel) {
8469 				s = t.getSel();
8470 
8471 				if (s) {
8472 					t.explicitRange = r;
8473 
8474 					try {
8475 						s.removeAllRanges();
8476 					} catch (ex) {
8477 						// IE9 might throw errors here don't know why
8478 					}
8479 
8480 					s.addRange(r);
8481 
8482 					// Forward is set to false and we have an extend function
8483 					if (forward === false && s.extend) {
8484 						s.collapse(r.endContainer, r.endOffset);
8485 						s.extend(r.startContainer, r.startOffset);
8486 					}
8487 
8488 					// adding range isn't always successful so we need to check range count otherwise an exception can occur
8489 					t.selectedRange = s.rangeCount > 0 ? s.getRangeAt(0) : null;
8490 				}
8491 			} else {
8492 				// Is W3C Range
8493 				if (r.cloneRange) {
8494 					try {
8495 						t.tridentSel.addRange(r);
8496 						return;
8497 					} catch (ex) {
8498 						//IE9 throws an error here if called before selection is placed in the editor
8499 					}
8500 				}
8501 
8502 				// Is IE specific range
8503 				try {
8504 					r.select();
8505 				} catch (ex) {
8506 					// Needed for some odd IE bug #1843306
8507 				}
8508 			}
8509 		},
8510 
8511 		setNode : function(n) {
8512 			var t = this;
8513 
8514 			t.setContent(t.dom.getOuterHTML(n));
8515 
8516 			return n;
8517 		},
8518 
8519 		getNode : function() {
8520 			var t = this, rng = t.getRng(), sel = t.getSel(), elm, start = rng.startContainer, end = rng.endContainer;
8521 
8522 			function skipEmptyTextNodes(n, forwards) {
8523 				var orig = n;
8524 				while (n && n.nodeType === 3 && n.length === 0) {
8525 					n = forwards ? n.nextSibling : n.previousSibling;
8526 				}
8527 				return n || orig;
8528 			};
8529 
8530 			// Range maybe lost after the editor is made visible again
8531 			if (!rng)
8532 				return t.dom.getRoot();
8533 
8534 			if (rng.setStart) {
8535 				elm = rng.commonAncestorContainer;
8536 
8537 				// Handle selection a image or other control like element such as anchors
8538 				if (!rng.collapsed) {
8539 					if (rng.startContainer == rng.endContainer) {
8540 						if (rng.endOffset - rng.startOffset < 2) {
8541 							if (rng.startContainer.hasChildNodes())
8542 								elm = rng.startContainer.childNodes[rng.startOffset];
8543 						}
8544 					}
8545 
8546 					// If the anchor node is a element instead of a text node then return this element
8547 					//if (tinymce.isWebKit && sel.anchorNode && sel.anchorNode.nodeType == 1)
8548 					//	return sel.anchorNode.childNodes[sel.anchorOffset];
8549 
8550 					// Handle cases where the selection is immediately wrapped around a node and return that node instead of it's parent.
8551 					// This happens when you double click an underlined word in FireFox.
8552 					if (start.nodeType === 3 && end.nodeType === 3) {
8553 						if (start.length === rng.startOffset) {
8554 							start = skipEmptyTextNodes(start.nextSibling, true);
8555 						} else {
8556 							start = start.parentNode;
8557 						}
8558 						if (rng.endOffset === 0) {
8559 							end = skipEmptyTextNodes(end.previousSibling, false);
8560 						} else {
8561 							end = end.parentNode;
8562 						}
8563 
8564 						if (start && start === end)
8565 							return start;
8566 					}
8567 				}
8568 
8569 				if (elm && elm.nodeType == 3)
8570 					return elm.parentNode;
8571 
8572 				return elm;
8573 			}
8574 
8575 			return rng.item ? rng.item(0) : rng.parentElement();
8576 		},
8577 
8578 		getSelectedBlocks : function(st, en) {
8579 			var t = this, dom = t.dom, sb, eb, n, bl = [];
8580 
8581 			sb = dom.getParent(st || t.getStart(), dom.isBlock);
8582 			eb = dom.getParent(en || t.getEnd(), dom.isBlock);
8583 
8584 			if (sb)
8585 				bl.push(sb);
8586 
8587 			if (sb && eb && sb != eb) {
8588 				n = sb;
8589 
8590 				var walker = new TreeWalker(sb, dom.getRoot());
8591 				while ((n = walker.next()) && n != eb) {
8592 					if (dom.isBlock(n))
8593 						bl.push(n);
8594 				}
8595 			}
8596 
8597 			if (eb && sb != eb)
8598 				bl.push(eb);
8599 
8600 			return bl;
8601 		},
8602 
8603 		isForward: function(){
8604 			var dom = this.dom, sel = this.getSel(), anchorRange, focusRange;
8605 
8606 			// No support for selection direction then always return true
8607 			if (!sel || sel.anchorNode == null || sel.focusNode == null) {
8608 				return true;
8609 			}
8610 
8611 			anchorRange = dom.createRng();
8612 			anchorRange.setStart(sel.anchorNode, sel.anchorOffset);
8613 			anchorRange.collapse(true);
8614 
8615 			focusRange = dom.createRng();
8616 			focusRange.setStart(sel.focusNode, sel.focusOffset);
8617 			focusRange.collapse(true);
8618 
8619 			return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
8620 		},
8621 
8622 		normalize : function() {
8623 			var self = this, rng, normalized, collapsed, node, sibling;
8624 
8625 			function normalizeEndPoint(start) {
8626 				var container, offset, walker, dom = self.dom, body = dom.getRoot(), node, nonEmptyElementsMap, nodeName;
8627 
8628 				function hasBrBeforeAfter(node, left) {
8629 					var walker = new TreeWalker(node, dom.getParent(node.parentNode, dom.isBlock) || body);
8630 
8631 					while (node = walker[left ? 'prev' : 'next']()) {
8632 						if (node.nodeName === "BR") {
8633 							return true;
8634 						}
8635 					}
8636 				};
8637 
8638 				// Walks the dom left/right to find a suitable text node to move the endpoint into
8639 				// It will only walk within the current parent block or body and will stop if it hits a block or a BR/IMG
8640 				function findTextNodeRelative(left, startNode) {
8641 					var walker, lastInlineElement;
8642 
8643 					startNode = startNode || container;
8644 					walker = new TreeWalker(startNode, dom.getParent(startNode.parentNode, dom.isBlock) || body);
8645 
8646 					// Walk left until we hit a text node we can move to or a block/br/img
8647 					while (node = walker[left ? 'prev' : 'next']()) {
8648 						// Found text node that has a length
8649 						if (node.nodeType === 3 && node.nodeValue.length > 0) {
8650 							container = node;
8651 							offset = left ? node.nodeValue.length : 0;
8652 							normalized = true;
8653 							return;
8654 						}
8655 
8656 						// Break if we find a block or a BR/IMG/INPUT etc
8657 						if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
8658 							return;
8659 						}
8660 
8661 						lastInlineElement = node;
8662 					}
8663 
8664 					// Only fetch the last inline element when in caret mode for now
8665 					if (collapsed && lastInlineElement) {
8666 						container = lastInlineElement;
8667 						normalized = true;
8668 						offset = 0;
8669 					}
8670 				};
8671 
8672 				container = rng[(start ? 'start' : 'end') + 'Container'];
8673 				offset = rng[(start ? 'start' : 'end') + 'Offset'];
8674 				nonEmptyElementsMap = dom.schema.getNonEmptyElements();
8675 
8676 				// If the container is a document move it to the body element
8677 				if (container.nodeType === 9) {
8678 					container = dom.getRoot();
8679 					offset = 0;
8680 				}
8681 
8682 				// If the container is body try move it into the closest text node or position
8683 				if (container === body) {
8684 					// If start is before/after a image, table etc
8685 					if (start) {
8686 						node = container.childNodes[offset > 0 ? offset - 1 : 0];
8687 						if (node) {
8688 							nodeName = node.nodeName.toLowerCase();
8689 							if (nonEmptyElementsMap[node.nodeName] || node.nodeName == "TABLE") {
8690 								return;
8691 							}
8692 						}
8693 					}
8694 
8695 					// Resolve the index
8696 					if (container.hasChildNodes()) {
8697 						container = container.childNodes[Math.min(!start && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1)];
8698 						offset = 0;
8699 
8700 						// Don't walk into elements that doesn't have any child nodes like a IMG
8701 						if (container.hasChildNodes() && !/TABLE/.test(container.nodeName)) {
8702 							// Walk the DOM to find a text node to place the caret at or a BR
8703 							node = container;
8704 							walker = new TreeWalker(container, body);
8705 
8706 							do {
8707 								// Found a text node use that position
8708 								if (node.nodeType === 3 && node.nodeValue.length > 0) {
8709 									offset = start ? 0 : node.nodeValue.length;
8710 									container = node;
8711 									normalized = true;
8712 									break;
8713 								}
8714 
8715 								// Found a BR/IMG element that we can place the caret before
8716 								if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
8717 									offset = dom.nodeIndex(node);
8718 									container = node.parentNode;
8719 
8720 									// Put caret after image when moving the end point
8721 									if (node.nodeName ==  "IMG" && !start) {
8722 										offset++;
8723 									}
8724 
8725 									normalized = true;
8726 									break;
8727 								}
8728 							} while (node = (start ? walker.next() : walker.prev()));
8729 						}
8730 					}
8731 				}
8732 
8733 				// Lean the caret to the left if possible
8734 				if (collapsed) {
8735 					// So this: <b>x</b><i>|x</i>
8736 					// Becomes: <b>x|</b><i>x</i>
8737 					// Seems that only gecko has issues with this
8738 					if (container.nodeType === 3 && offset === 0) {
8739 						findTextNodeRelative(true);
8740 					}
8741 
8742 					// Lean left into empty inline elements when the caret is before a BR
8743 					// So this: <i><b></b><i>|<br></i>
8744 					// Becomes: <i><b>|</b><i><br></i>
8745 					// Seems that only gecko has issues with this
8746 					if (container.nodeType === 1) {
8747 						node = container.childNodes[offset];
8748 						if(node && node.nodeName === 'BR' && !hasBrBeforeAfter(node) && !hasBrBeforeAfter(node, true)) {
8749 							findTextNodeRelative(true, container.childNodes[offset]);
8750 						}
8751 					}
8752 				}
8753 
8754 				// Lean the start of the selection right if possible
8755 				// So this: x[<b>x]</b>
8756 				// Becomes: x<b>[x]</b>
8757 				if (start && !collapsed && container.nodeType === 3 && offset === container.nodeValue.length) {
8758 					findTextNodeRelative(false);
8759 				}
8760 
8761 				// Set endpoint if it was normalized
8762 				if (normalized)
8763 					rng['set' + (start ? 'Start' : 'End')](container, offset);
8764 			};
8765 
8766 			// Normalize only on non IE browsers for now
8767 			if (tinymce.isIE)
8768 				return;
8769 			
8770 			rng = self.getRng();
8771 			collapsed = rng.collapsed;
8772 
8773 			// Normalize the end points
8774 			normalizeEndPoint(true);
8775 
8776 			if (!collapsed)
8777 				normalizeEndPoint();
8778 
8779 			// Set the selection if it was normalized
8780 			if (normalized) {
8781 				// If it was collapsed then make sure it still is
8782 				if (collapsed) {
8783 					rng.collapse(true);
8784 				}
8785 
8786 				//console.log(self.dom.dumpRng(rng));
8787 				self.setRng(rng, self.isForward());
8788 			}
8789 		},
8790 
8791 		selectorChanged: function(selector, callback) {
8792 			var self = this, currentSelectors;
8793 
8794 			if (!self.selectorChangedData) {
8795 				self.selectorChangedData = {};
8796 				currentSelectors = {};
8797 
8798 				self.editor.onNodeChange.addToTop(function(ed, cm, node) {
8799 					var dom = self.dom, parents = dom.getParents(node, null, dom.getRoot()), matchedSelectors = {};
8800 
8801 					// Check for new matching selectors
8802 					each(self.selectorChangedData, function(callbacks, selector) {
8803 						each(parents, function(node) {
8804 							if (dom.is(node, selector)) {
8805 								if (!currentSelectors[selector]) {
8806 									// Execute callbacks
8807 									each(callbacks, function(callback) {
8808 										callback(true, {node: node, selector: selector, parents: parents});
8809 									});
8810 
8811 									currentSelectors[selector] = callbacks;
8812 								}
8813 
8814 								matchedSelectors[selector] = callbacks;
8815 								return false;
8816 							}
8817 						});
8818 					});
8819 
8820 					// Check if current selectors still match
8821 					each(currentSelectors, function(callbacks, selector) {
8822 						if (!matchedSelectors[selector]) {
8823 							delete currentSelectors[selector];
8824 
8825 							each(callbacks, function(callback) {
8826 								callback(false, {node: node, selector: selector, parents: parents});
8827 							});
8828 						}
8829 					});
8830 				});
8831 			}
8832 
8833 			// Add selector listeners
8834 			if (!self.selectorChangedData[selector]) {
8835 				self.selectorChangedData[selector] = [];
8836 			}
8837 
8838 			self.selectorChangedData[selector].push(callback);
8839 
8840 			return self;
8841 		},
8842 
8843 		destroy : function(manual) {
8844 			var self = this;
8845 
8846 			self.win = null;
8847 
8848 			// Manual destroy then remove unload handler
8849 			if (!manual)
8850 				tinymce.removeUnload(self.destroy);
8851 		},
8852 
8853 		// IE has an issue where you can't select/move the caret by clicking outside the body if the document is in standards mode
8854 		_fixIESelection : function() {
8855 			var dom = this.dom, doc = dom.doc, body = doc.body, started, startRng, htmlElm;
8856 
8857 			// Return range from point or null if it failed
8858 			function rngFromPoint(x, y) {
8859 				var rng = body.createTextRange();
8860 
8861 				try {
8862 					rng.moveToPoint(x, y);
8863 				} catch (ex) {
8864 					// IE sometimes throws and exception, so lets just ignore it
8865 					rng = null;
8866 				}
8867 
8868 				return rng;
8869 			};
8870 
8871 			// Fires while the selection is changing
8872 			function selectionChange(e) {
8873 				var pointRng;
8874 
8875 				// Check if the button is down or not
8876 				if (e.button) {
8877 					// Create range from mouse position
8878 					pointRng = rngFromPoint(e.x, e.y);
8879 
8880 					if (pointRng) {
8881 						// Check if pointRange is before/after selection then change the endPoint
8882 						if (pointRng.compareEndPoints('StartToStart', startRng) > 0)
8883 							pointRng.setEndPoint('StartToStart', startRng);
8884 						else
8885 							pointRng.setEndPoint('EndToEnd', startRng);
8886 
8887 						pointRng.select();
8888 					}
8889 				} else
8890 					endSelection();
8891 			}
8892 
8893 			// Removes listeners
8894 			function endSelection() {
8895 				var rng = doc.selection.createRange();
8896 
8897 				// If the range is collapsed then use the last start range
8898 				if (startRng && !rng.item && rng.compareEndPoints('StartToEnd', rng) === 0)
8899 					startRng.select();
8900 
8901 				dom.unbind(doc, 'mouseup', endSelection);
8902 				dom.unbind(doc, 'mousemove', selectionChange);
8903 				startRng = started = 0;
8904 			};
8905 
8906 			// Make HTML element unselectable since we are going to handle selection by hand
8907 			doc.documentElement.unselectable = true;
8908 			
8909 			// Detect when user selects outside BODY
8910 			dom.bind(doc, ['mousedown', 'contextmenu'], function(e) {
8911 				if (e.target.nodeName === 'HTML') {
8912 					if (started)
8913 						endSelection();
8914 
8915 					// Detect vertical scrollbar, since IE will fire a mousedown on the scrollbar and have target set as HTML
8916 					htmlElm = doc.documentElement;
8917 					if (htmlElm.scrollHeight > htmlElm.clientHeight)
8918 						return;
8919 
8920 					started = 1;
8921 					// Setup start position
8922 					startRng = rngFromPoint(e.x, e.y);
8923 					if (startRng) {
8924 						// Listen for selection change events
8925 						dom.bind(doc, 'mouseup', endSelection);
8926 						dom.bind(doc, 'mousemove', selectionChange);
8927 
8928 						dom.win.focus();
8929 						startRng.select();
8930 					}
8931 				}
8932 			});
8933 		}
8934 	});
8935 })(tinymce);
8936 
8937 (function(tinymce) {
8938 	tinymce.dom.Serializer = function(settings, dom, schema) {
8939 		var onPreProcess, onPostProcess, isIE = tinymce.isIE, each = tinymce.each, htmlParser;
8940 
8941 		// Support the old apply_source_formatting option
8942 		if (!settings.apply_source_formatting)
8943 			settings.indent = false;
8944 
8945 		// Default DOM and Schema if they are undefined
8946 		dom = dom || tinymce.DOM;
8947 		schema = schema || new tinymce.html.Schema(settings);
8948 		settings.entity_encoding = settings.entity_encoding || 'named';
8949 		settings.remove_trailing_brs = "remove_trailing_brs" in settings ? settings.remove_trailing_brs : true;
8950 
8951 		onPreProcess = new tinymce.util.Dispatcher(self);
8952 
8953 		onPostProcess = new tinymce.util.Dispatcher(self);
8954 
8955 		htmlParser = new tinymce.html.DomParser(settings, schema);
8956 
8957 		// Convert move data-mce-src, data-mce-href and data-mce-style into nodes or process them if needed
8958 		htmlParser.addAttributeFilter('src,href,style', function(nodes, name) {
8959 			var i = nodes.length, node, value, internalName = 'data-mce-' + name, urlConverter = settings.url_converter, urlConverterScope = settings.url_converter_scope, undef;
8960 
8961 			while (i--) {
8962 				node = nodes[i];
8963 
8964 				value = node.attributes.map[internalName];
8965 				if (value !== undef) {
8966 					// Set external name to internal value and remove internal
8967 					node.attr(name, value.length > 0 ? value : null);
8968 					node.attr(internalName, null);
8969 				} else {
8970 					// No internal attribute found then convert the value we have in the DOM
8971 					value = node.attributes.map[name];
8972 
8973 					if (name === "style")
8974 						value = dom.serializeStyle(dom.parseStyle(value), node.name);
8975 					else if (urlConverter)
8976 						value = urlConverter.call(urlConverterScope, value, name, node.name);
8977 
8978 					node.attr(name, value.length > 0 ? value : null);
8979 				}
8980 			}
8981 		});
8982 
8983 		// Remove internal classes mceItem<..> or mceSelected
8984 		htmlParser.addAttributeFilter('class', function(nodes, name) {
8985 			var i = nodes.length, node, value;
8986 
8987 			while (i--) {
8988 				node = nodes[i];
8989 				value = node.attr('class').replace(/(?:^|\s)mce(Item\w+|Selected)(?!\S)/g, '');
8990 				node.attr('class', value.length > 0 ? value : null);
8991 			}
8992 		});
8993 
8994 		// Remove bookmark elements
8995 		htmlParser.addAttributeFilter('data-mce-type', function(nodes, name, args) {
8996 			var i = nodes.length, node;
8997 
8998 			while (i--) {
8999 				node = nodes[i];
9000 
9001 				if (node.attributes.map['data-mce-type'] === 'bookmark' && !args.cleanup)
9002 					node.remove();
9003 			}
9004 		});
9005 
9006 		// Remove expando attributes
9007 		htmlParser.addAttributeFilter('data-mce-expando', function(nodes, name, args) {
9008 			var i = nodes.length;
9009 
9010 			while (i--) {
9011 				nodes[i].attr(name, null);
9012 			}
9013 		});
9014 
9015 		// Force script into CDATA sections and remove the mce- prefix also add comments around styles
9016 		htmlParser.addNodeFilter('script,style', function(nodes, name) {
9017 			var i = nodes.length, node, value;
9018 
9019 			function trim(value) {
9020 				return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n')
9021 						.replace(/^[\r\n]*|[\r\n]*$/g, '')
9022 						.replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '')
9023 						.replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
9024 			};
9025 
9026 			while (i--) {
9027 				node = nodes[i];
9028 				value = node.firstChild ? node.firstChild.value : '';
9029 
9030 				if (name === "script") {
9031 					// Remove mce- prefix from script elements
9032 					node.attr('type', (node.attr('type') || 'text/javascript').replace(/^mce\-/, ''));
9033 
9034 					if (value.length > 0)
9035 						node.firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
9036 				} else {
9037 					if (value.length > 0)
9038 						node.firstChild.value = '<!--\n' + trim(value) + '\n-->';
9039 				}
9040 			}
9041 		});
9042 
9043 		// Convert comments to cdata and handle protected comments
9044 		htmlParser.addNodeFilter('#comment', function(nodes, name) {
9045 			var i = nodes.length, node;
9046 
9047 			while (i--) {
9048 				node = nodes[i];
9049 
9050 				if (node.value.indexOf('[CDATA[') === 0) {
9051 					node.name = '#cdata';
9052 					node.type = 4;
9053 					node.value = node.value.replace(/^\[CDATA\[|\]\]$/g, '');
9054 				} else if (node.value.indexOf('mce:protected ') === 0) {
9055 					node.name = "#text";
9056 					node.type = 3;
9057 					node.raw = true;
9058 					node.value = unescape(node.value).substr(14);
9059 				}
9060 			}
9061 		});
9062 
9063 		htmlParser.addNodeFilter('xml:namespace,input', function(nodes, name) {
9064 			var i = nodes.length, node;
9065 
9066 			while (i--) {
9067 				node = nodes[i];
9068 				if (node.type === 7)
9069 					node.remove();
9070 				else if (node.type === 1) {
9071 					if (name === "input" && !("type" in node.attributes.map))
9072 						node.attr('type', 'text');
9073 				}
9074 			}
9075 		});
9076 
9077 		// Fix list elements, TODO: Replace this later
9078 		if (settings.fix_list_elements) {
9079 			htmlParser.addNodeFilter('ul,ol', function(nodes, name) {
9080 				var i = nodes.length, node, parentNode;
9081 
9082 				while (i--) {
9083 					node = nodes[i];
9084 					parentNode = node.parent;
9085 
9086 					if (parentNode.name === 'ul' || parentNode.name === 'ol') {
9087 						if (node.prev && node.prev.name === 'li') {
9088 							node.prev.append(node);
9089 						}
9090 					}
9091 				}
9092 			});
9093 		}
9094 
9095 		// Remove internal data attributes
9096 		htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style', function(nodes, name) {
9097 			var i = nodes.length;
9098 
9099 			while (i--) {
9100 				nodes[i].attr(name, null);
9101 			}
9102 		});
9103 
9104 		// Return public methods
9105 		return {
9106 			schema : schema,
9107 
9108 			addNodeFilter : htmlParser.addNodeFilter,
9109 
9110 			addAttributeFilter : htmlParser.addAttributeFilter,
9111 
9112 			onPreProcess : onPreProcess,
9113 
9114 			onPostProcess : onPostProcess,
9115 
9116 			serialize : function(node, args) {
9117 				var impl, doc, oldDoc, htmlSerializer, content;
9118 
9119 				// Explorer won't clone contents of script and style and the
9120 				// selected index of select elements are cleared on a clone operation.
9121 				if (isIE && dom.select('script,style,select,map').length > 0) {
9122 					content = node.innerHTML;
9123 					node = node.cloneNode(false);
9124 					dom.setHTML(node, content);
9125 				} else
9126 					node = node.cloneNode(true);
9127 
9128 				// Nodes needs to be attached to something in WebKit/Opera
9129 				// Older builds of Opera crashes if you attach the node to an document created dynamically
9130 				// and since we can't feature detect a crash we need to sniff the acutal build number
9131 				// This fix will make DOM ranges and make Sizzle happy!
9132 				impl = node.ownerDocument.implementation;
9133 				if (impl.createHTMLDocument) {
9134 					// Create an empty HTML document
9135 					doc = impl.createHTMLDocument("");
9136 
9137 					// Add the element or it's children if it's a body element to the new document
9138 					each(node.nodeName == 'BODY' ? node.childNodes : [node], function(node) {
9139 						doc.body.appendChild(doc.importNode(node, true));
9140 					});
9141 
9142 					// Grab first child or body element for serialization
9143 					if (node.nodeName != 'BODY')
9144 						node = doc.body.firstChild;
9145 					else
9146 						node = doc.body;
9147 
9148 					// set the new document in DOMUtils so createElement etc works
9149 					oldDoc = dom.doc;
9150 					dom.doc = doc;
9151 				}
9152 
9153 				args = args || {};
9154 				args.format = args.format || 'html';
9155 
9156 				// Pre process
9157 				if (!args.no_events) {
9158 					args.node = node;
9159 					onPreProcess.dispatch(self, args);
9160 				}
9161 
9162 				// Setup serializer
9163 				htmlSerializer = new tinymce.html.Serializer(settings, schema);
9164 
9165 				// Parse and serialize HTML
9166 				args.content = htmlSerializer.serialize(
9167 					htmlParser.parse(tinymce.trim(args.getInner ? node.innerHTML : dom.getOuterHTML(node)), args)
9168 				);
9169 
9170 				// Replace all BOM characters for now until we can find a better solution
9171 				if (!args.cleanup)
9172 					args.content = args.content.replace(/\uFEFF|\u200B/g, '');
9173 
9174 				// Post process
9175 				if (!args.no_events)
9176 					onPostProcess.dispatch(self, args);
9177 
9178 				// Restore the old document if it was changed
9179 				if (oldDoc)
9180 					dom.doc = oldDoc;
9181 
9182 				args.node = null;
9183 
9184 				return args.content;
9185 			},
9186 
9187 			addRules : function(rules) {
9188 				schema.addValidElements(rules);
9189 			},
9190 
9191 			setRules : function(rules) {
9192 				schema.setValidElements(rules);
9193 			}
9194 		};
9195 	};
9196 })(tinymce);
9197 (function(tinymce) {
9198 	tinymce.dom.ScriptLoader = function(settings) {
9199 		var QUEUED = 0,
9200 			LOADING = 1,
9201 			LOADED = 2,
9202 			states = {},
9203 			queue = [],
9204 			scriptLoadedCallbacks = {},
9205 			queueLoadedCallbacks = [],
9206 			loading = 0,
9207 			undef;
9208 
9209 		function loadScript(url, callback) {
9210 			var t = this, dom = tinymce.DOM, elm, uri, loc, id;
9211 
9212 			// Execute callback when script is loaded
9213 			function done() {
9214 				dom.remove(id);
9215 
9216 				if (elm)
9217 					elm.onreadystatechange = elm.onload = elm = null;
9218 
9219 				callback();
9220 			};
9221 			
9222 			function error() {
9223 				// Report the error so it's easier for people to spot loading errors
9224 				if (typeof(console) !== "undefined" && console.log)
9225 					console.log("Failed to load: " + url);
9226 
9227 				// We can't mark it as done if there is a load error since
9228 				// A) We don't want to produce 404 errors on the server and
9229 				// B) the onerror event won't fire on all browsers.
9230 				// done();
9231 			};
9232 
9233 			id = dom.uniqueId();
9234 
9235 			if (tinymce.isIE6) {
9236 				uri = new tinymce.util.URI(url);
9237 				loc = location;
9238 
9239 				// If script is from same domain and we
9240 				// use IE 6 then use XHR since it's more reliable
9241 				if (uri.host == loc.hostname && uri.port == loc.port && (uri.protocol + ':') == loc.protocol && uri.protocol.toLowerCase() != 'file') {
9242 					tinymce.util.XHR.send({
9243 						url : tinymce._addVer(uri.getURI()),
9244 						success : function(content) {
9245 							// Create new temp script element
9246 							var script = dom.create('script', {
9247 								type : 'text/javascript'
9248 							});
9249 
9250 							// Evaluate script in global scope
9251 							script.text = content;
9252 							document.getElementsByTagName('head')[0].appendChild(script);
9253 							dom.remove(script);
9254 
9255 							done();
9256 						},
9257 						
9258 						error : error
9259 					});
9260 
9261 					return;
9262 				}
9263 			}
9264 
9265 			// Create new script element
9266 			elm = document.createElement('script');
9267 			elm.id = id;
9268 			elm.type = 'text/javascript';
9269 			elm.src = tinymce._addVer(url);
9270 
9271 			// Add onload listener for non IE browsers since IE9
9272 			// fires onload event before the script is parsed and executed
9273 			if (!tinymce.isIE)
9274 				elm.onload = done;
9275 
9276 			// Add onerror event will get fired on some browsers but not all of them
9277 			elm.onerror = error;
9278 
9279 			// Opera 9.60 doesn't seem to fire the onreadystate event at correctly
9280 			if (!tinymce.isOpera) {
9281 				elm.onreadystatechange = function() {
9282 					var state = elm.readyState;
9283 
9284 					// Loaded state is passed on IE 6 however there
9285 					// are known issues with this method but we can't use
9286 					// XHR in a cross domain loading
9287 					if (state == 'complete' || state == 'loaded')
9288 						done();
9289 				};
9290 			}
9291 
9292 			// Most browsers support this feature so we report errors
9293 			// for those at least to help users track their missing plugins etc
9294 			// todo: Removed since it produced error if the document is unloaded by navigating away, re-add it as an option
9295 			/*elm.onerror = function() {
9296 				alert('Failed to load: ' + url);
9297 			};*/
9298 
9299 			// Add script to document
9300 			(document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
9301 		};
9302 
9303 		this.isDone = function(url) {
9304 			return states[url] == LOADED;
9305 		};
9306 
9307 		this.markDone = function(url) {
9308 			states[url] = LOADED;
9309 		};
9310 
9311 		this.add = this.load = function(url, callback, scope) {
9312 			var item, state = states[url];
9313 
9314 			// Add url to load queue
9315 			if (state == undef) {
9316 				queue.push(url);
9317 				states[url] = QUEUED;
9318 			}
9319 
9320 			if (callback) {
9321 				// Store away callback for later execution
9322 				if (!scriptLoadedCallbacks[url])
9323 					scriptLoadedCallbacks[url] = [];
9324 
9325 				scriptLoadedCallbacks[url].push({
9326 					func : callback,
9327 					scope : scope || this
9328 				});
9329 			}
9330 		};
9331 
9332 		this.loadQueue = function(callback, scope) {
9333 			this.loadScripts(queue, callback, scope);
9334 		};
9335 
9336 		this.loadScripts = function(scripts, callback, scope) {
9337 			var loadScripts;
9338 
9339 			function execScriptLoadedCallbacks(url) {
9340 				// Execute URL callback functions
9341 				tinymce.each(scriptLoadedCallbacks[url], function(callback) {
9342 					callback.func.call(callback.scope);
9343 				});
9344 
9345 				scriptLoadedCallbacks[url] = undef;
9346 			};
9347 
9348 			queueLoadedCallbacks.push({
9349 				func : callback,
9350 				scope : scope || this
9351 			});
9352 
9353 			loadScripts = function() {
9354 				var loadingScripts = tinymce.grep(scripts);
9355 
9356 				// Current scripts has been handled
9357 				scripts.length = 0;
9358 
9359 				// Load scripts that needs to be loaded
9360 				tinymce.each(loadingScripts, function(url) {
9361 					// Script is already loaded then execute script callbacks directly
9362 					if (states[url] == LOADED) {
9363 						execScriptLoadedCallbacks(url);
9364 						return;
9365 					}
9366 
9367 					// Is script not loading then start loading it
9368 					if (states[url] != LOADING) {
9369 						states[url] = LOADING;
9370 						loading++;
9371 
9372 						loadScript(url, function() {
9373 							states[url] = LOADED;
9374 							loading--;
9375 
9376 							execScriptLoadedCallbacks(url);
9377 
9378 							// Load more scripts if they where added by the recently loaded script
9379 							loadScripts();
9380 						});
9381 					}
9382 				});
9383 
9384 				// No scripts are currently loading then execute all pending queue loaded callbacks
9385 				if (!loading) {
9386 					tinymce.each(queueLoadedCallbacks, function(callback) {
9387 						callback.func.call(callback.scope);
9388 					});
9389 
9390 					queueLoadedCallbacks.length = 0;
9391 				}
9392 			};
9393 
9394 			loadScripts();
9395 		};
9396 	};
9397 
9398 	// Global script loader
9399 	tinymce.ScriptLoader = new tinymce.dom.ScriptLoader();
9400 })(tinymce);
9401 
9402 (function(tinymce) {
9403 	tinymce.dom.RangeUtils = function(dom) {
9404 		var INVISIBLE_CHAR = '\uFEFF';
9405 
9406 		this.walk = function(rng, callback) {
9407 			var startContainer = rng.startContainer,
9408 				startOffset = rng.startOffset,
9409 				endContainer = rng.endContainer,
9410 				endOffset = rng.endOffset,
9411 				ancestor, startPoint,
9412 				endPoint, node, parent, siblings, nodes;
9413 
9414 			// Handle table cell selection the table plugin enables
9415 			// you to fake select table cells and perform formatting actions on them
9416 			nodes = dom.select('td.mceSelected,th.mceSelected');
9417 			if (nodes.length > 0) {
9418 				tinymce.each(nodes, function(node) {
9419 					callback([node]);
9420 				});
9421 
9422 				return;
9423 			}
9424 
9425 			function exclude(nodes) {
9426 				var node;
9427 
9428 				// First node is excluded
9429 				node = nodes[0];
9430 				if (node.nodeType === 3 && node === startContainer && startOffset >= node.nodeValue.length) {
9431 					nodes.splice(0, 1);
9432 				}
9433 
9434 				// Last node is excluded
9435 				node = nodes[nodes.length - 1];
9436 				if (endOffset === 0 && nodes.length > 0 && node === endContainer && node.nodeType === 3) {
9437 					nodes.splice(nodes.length - 1, 1);
9438 				}
9439 
9440 				return nodes;
9441 			};
9442 
9443 			function collectSiblings(node, name, end_node) {
9444 				var siblings = [];
9445 
9446 				for (; node && node != end_node; node = node[name])
9447 					siblings.push(node);
9448 
9449 				return siblings;
9450 			};
9451 
9452 			function findEndPoint(node, root) {
9453 				do {
9454 					if (node.parentNode == root)
9455 						return node;
9456 
9457 					node = node.parentNode;
9458 				} while(node);
9459 			};
9460 
9461 			function walkBoundary(start_node, end_node, next) {
9462 				var siblingName = next ? 'nextSibling' : 'previousSibling';
9463 
9464 				for (node = start_node, parent = node.parentNode; node && node != end_node; node = parent) {
9465 					parent = node.parentNode;
9466 					siblings = collectSiblings(node == start_node ? node : node[siblingName], siblingName);
9467 
9468 					if (siblings.length) {
9469 						if (!next)
9470 							siblings.reverse();
9471 
9472 						callback(exclude(siblings));
9473 					}
9474 				}
9475 			};
9476 
9477 			// If index based start position then resolve it
9478 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes())
9479 				startContainer = startContainer.childNodes[startOffset];
9480 
9481 			// If index based end position then resolve it
9482 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes())
9483 				endContainer = endContainer.childNodes[Math.min(endOffset - 1, endContainer.childNodes.length - 1)];
9484 
9485 			// Same container
9486 			if (startContainer == endContainer)
9487 				return callback(exclude([startContainer]));
9488 
9489 			// Find common ancestor and end points
9490 			ancestor = dom.findCommonAncestor(startContainer, endContainer);
9491 				
9492 			// Process left side
9493 			for (node = startContainer; node; node = node.parentNode) {
9494 				if (node === endContainer)
9495 					return walkBoundary(startContainer, ancestor, true);
9496 
9497 				if (node === ancestor)
9498 					break;
9499 			}
9500 
9501 			// Process right side
9502 			for (node = endContainer; node; node = node.parentNode) {
9503 				if (node === startContainer)
9504 					return walkBoundary(endContainer, ancestor);
9505 
9506 				if (node === ancestor)
9507 					break;
9508 			}
9509 
9510 			// Find start/end point
9511 			startPoint = findEndPoint(startContainer, ancestor) || startContainer;
9512 			endPoint = findEndPoint(endContainer, ancestor) || endContainer;
9513 
9514 			// Walk left leaf
9515 			walkBoundary(startContainer, startPoint, true);
9516 
9517 			// Walk the middle from start to end point
9518 			siblings = collectSiblings(
9519 				startPoint == startContainer ? startPoint : startPoint.nextSibling,
9520 				'nextSibling',
9521 				endPoint == endContainer ? endPoint.nextSibling : endPoint
9522 			);
9523 
9524 			if (siblings.length)
9525 				callback(exclude(siblings));
9526 
9527 			// Walk right leaf
9528 			walkBoundary(endContainer, endPoint);
9529 		};
9530 
9531 		this.split = function(rng) {
9532 			var startContainer = rng.startContainer,
9533 				startOffset = rng.startOffset,
9534 				endContainer = rng.endContainer,
9535 				endOffset = rng.endOffset;
9536 
9537 			function splitText(node, offset) {
9538 				return node.splitText(offset);
9539 			};
9540 
9541 			// Handle single text node
9542 			if (startContainer == endContainer && startContainer.nodeType == 3) {
9543 				if (startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9544 					endContainer = splitText(startContainer, startOffset);
9545 					startContainer = endContainer.previousSibling;
9546 
9547 					if (endOffset > startOffset) {
9548 						endOffset = endOffset - startOffset;
9549 						startContainer = endContainer = splitText(endContainer, endOffset).previousSibling;
9550 						endOffset = endContainer.nodeValue.length;
9551 						startOffset = 0;
9552 					} else {
9553 						endOffset = 0;
9554 					}
9555 				}
9556 			} else {
9557 				// Split startContainer text node if needed
9558 				if (startContainer.nodeType == 3 && startOffset > 0 && startOffset < startContainer.nodeValue.length) {
9559 					startContainer = splitText(startContainer, startOffset);
9560 					startOffset = 0;
9561 				}
9562 
9563 				// Split endContainer text node if needed
9564 				if (endContainer.nodeType == 3 && endOffset > 0 && endOffset < endContainer.nodeValue.length) {
9565 					endContainer = splitText(endContainer, endOffset).previousSibling;
9566 					endOffset = endContainer.nodeValue.length;
9567 				}
9568 			}
9569 
9570 			return {
9571 				startContainer : startContainer,
9572 				startOffset : startOffset,
9573 				endContainer : endContainer,
9574 				endOffset : endOffset
9575 			};
9576 		};
9577 
9578 	};
9579 
9580 	tinymce.dom.RangeUtils.compareRanges = function(rng1, rng2) {
9581 		if (rng1 && rng2) {
9582 			// Compare native IE ranges
9583 			if (rng1.item || rng1.duplicate) {
9584 				// Both are control ranges and the selected element matches
9585 				if (rng1.item && rng2.item && rng1.item(0) === rng2.item(0))
9586 					return true;
9587 
9588 				// Both are text ranges and the range matches
9589 				if (rng1.isEqual && rng2.isEqual && rng2.isEqual(rng1))
9590 					return true;
9591 			} else {
9592 				// Compare w3c ranges
9593 				return rng1.startContainer == rng2.startContainer && rng1.startOffset == rng2.startOffset;
9594 			}
9595 		}
9596 
9597 		return false;
9598 	};
9599 })(tinymce);
9600 
9601 (function(tinymce) {
9602 	var Event = tinymce.dom.Event, each = tinymce.each;
9603 
9604 	tinymce.create('tinymce.ui.KeyboardNavigation', {
9605 		KeyboardNavigation: function(settings, dom) {
9606 			var t = this, root = settings.root, items = settings.items,
9607 					enableUpDown = settings.enableUpDown, enableLeftRight = settings.enableLeftRight || !settings.enableUpDown,
9608 					excludeFromTabOrder = settings.excludeFromTabOrder,
9609 					itemFocussed, itemBlurred, rootKeydown, rootFocussed, focussedId;
9610 
9611 			dom = dom || tinymce.DOM;
9612 
9613 			itemFocussed = function(evt) {
9614 				focussedId = evt.target.id;
9615 			};
9616 			
9617 			itemBlurred = function(evt) {
9618 				dom.setAttrib(evt.target.id, 'tabindex', '-1');
9619 			};
9620 			
9621 			rootFocussed = function(evt) {
9622 				var item = dom.get(focussedId);
9623 				dom.setAttrib(item, 'tabindex', '0');
9624 				item.focus();
9625 			};
9626 			
9627 			t.focus = function() {
9628 				dom.get(focussedId).focus();
9629 			};
9630 
9631 			t.destroy = function() {
9632 				each(items, function(item) {
9633 					var elm = dom.get(item.id);
9634 
9635 					dom.unbind(elm, 'focus', itemFocussed);
9636 					dom.unbind(elm, 'blur', itemBlurred);
9637 				});
9638 
9639 				var rootElm = dom.get(root);
9640 				dom.unbind(rootElm, 'focus', rootFocussed);
9641 				dom.unbind(rootElm, 'keydown', rootKeydown);
9642 
9643 				items = dom = root = t.focus = itemFocussed = itemBlurred = rootKeydown = rootFocussed = null;
9644 				t.destroy = function() {};
9645 			};
9646 			
9647 			t.moveFocus = function(dir, evt) {
9648 				var idx = -1, controls = t.controls, newFocus;
9649 
9650 				if (!focussedId)
9651 					return;
9652 
9653 				each(items, function(item, index) {
9654 					if (item.id === focussedId) {
9655 						idx = index;
9656 						return false;
9657 					}
9658 				});
9659 
9660 				idx += dir;
9661 				if (idx < 0) {
9662 					idx = items.length - 1;
9663 				} else if (idx >= items.length) {
9664 					idx = 0;
9665 				}
9666 				
9667 				newFocus = items[idx];
9668 				dom.setAttrib(focussedId, 'tabindex', '-1');
9669 				dom.setAttrib(newFocus.id, 'tabindex', '0');
9670 				dom.get(newFocus.id).focus();
9671 
9672 				if (settings.actOnFocus) {
9673 					settings.onAction(newFocus.id);
9674 				}
9675 
9676 				if (evt)
9677 					Event.cancel(evt);
9678 			};
9679 			
9680 			rootKeydown = function(evt) {
9681 				var DOM_VK_LEFT = 37, DOM_VK_RIGHT = 39, DOM_VK_UP = 38, DOM_VK_DOWN = 40, DOM_VK_ESCAPE = 27, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_SPACE = 32;
9682 				
9683 				switch (evt.keyCode) {
9684 					case DOM_VK_LEFT:
9685 						if (enableLeftRight) t.moveFocus(-1);
9686 						break;
9687 	
9688 					case DOM_VK_RIGHT:
9689 						if (enableLeftRight) t.moveFocus(1);
9690 						break;
9691 	
9692 					case DOM_VK_UP:
9693 						if (enableUpDown) t.moveFocus(-1);
9694 						break;
9695 
9696 					case DOM_VK_DOWN:
9697 						if (enableUpDown) t.moveFocus(1);
9698 						break;
9699 
9700 					case DOM_VK_ESCAPE:
9701 						if (settings.onCancel) {
9702 							settings.onCancel();
9703 							Event.cancel(evt);
9704 						}
9705 						break;
9706 
9707 					case DOM_VK_ENTER:
9708 					case DOM_VK_RETURN:
9709 					case DOM_VK_SPACE:
9710 						if (settings.onAction) {
9711 							settings.onAction(focussedId);
9712 							Event.cancel(evt);
9713 						}
9714 						break;
9715 				}
9716 			};
9717 
9718 			// Set up state and listeners for each item.
9719 			each(items, function(item, idx) {
9720 				var tabindex, elm;
9721 
9722 				if (!item.id) {
9723 					item.id = dom.uniqueId('_mce_item_');
9724 				}
9725 
9726 				elm = dom.get(item.id);
9727 
9728 				if (excludeFromTabOrder) {
9729 					dom.bind(elm, 'blur', itemBlurred);
9730 					tabindex = '-1';
9731 				} else {
9732 					tabindex = (idx === 0 ? '0' : '-1');
9733 				}
9734 
9735 				elm.setAttribute('tabindex', tabindex);
9736 				dom.bind(elm, 'focus', itemFocussed);
9737 			});
9738 			
9739 			// Setup initial state for root element.
9740 			if (items[0]){
9741 				focussedId = items[0].id;
9742 			}
9743 
9744 			dom.setAttrib(root, 'tabindex', '-1');
9745 
9746 			// Setup listeners for root element.
9747 			var rootElm = dom.get(root);
9748 			dom.bind(rootElm, 'focus', rootFocussed);
9749 			dom.bind(rootElm, 'keydown', rootKeydown);
9750 		}
9751 	});
9752 })(tinymce);
9753 
9754 (function(tinymce) {
9755 	// Shorten class names
9756 	var DOM = tinymce.DOM, is = tinymce.is;
9757 
9758 	tinymce.create('tinymce.ui.Control', {
9759 		Control : function(id, s, editor) {
9760 			this.id = id;
9761 			this.settings = s = s || {};
9762 			this.rendered = false;
9763 			this.onRender = new tinymce.util.Dispatcher(this);
9764 			this.classPrefix = '';
9765 			this.scope = s.scope || this;
9766 			this.disabled = 0;
9767 			this.active = 0;
9768 			this.editor = editor;
9769 		},
9770 		
9771 		setAriaProperty : function(property, value) {
9772 			var element = DOM.get(this.id + '_aria') || DOM.get(this.id);
9773 			if (element) {
9774 				DOM.setAttrib(element, 'aria-' + property, !!value);
9775 			}
9776 		},
9777 		
9778 		focus : function() {
9779 			DOM.get(this.id).focus();
9780 		},
9781 
9782 		setDisabled : function(s) {
9783 			if (s != this.disabled) {
9784 				this.setAriaProperty('disabled', s);
9785 
9786 				this.setState('Disabled', s);
9787 				this.setState('Enabled', !s);
9788 				this.disabled = s;
9789 			}
9790 		},
9791 
9792 		isDisabled : function() {
9793 			return this.disabled;
9794 		},
9795 
9796 		setActive : function(s) {
9797 			if (s != this.active) {
9798 				this.setState('Active', s);
9799 				this.active = s;
9800 				this.setAriaProperty('pressed', s);
9801 			}
9802 		},
9803 
9804 		isActive : function() {
9805 			return this.active;
9806 		},
9807 
9808 		setState : function(c, s) {
9809 			var n = DOM.get(this.id);
9810 
9811 			c = this.classPrefix + c;
9812 
9813 			if (s)
9814 				DOM.addClass(n, c);
9815 			else
9816 				DOM.removeClass(n, c);
9817 		},
9818 
9819 		isRendered : function() {
9820 			return this.rendered;
9821 		},
9822 
9823 		renderHTML : function() {
9824 		},
9825 
9826 		renderTo : function(n) {
9827 			DOM.setHTML(n, this.renderHTML());
9828 		},
9829 
9830 		postRender : function() {
9831 			var t = this, b;
9832 
9833 			// Set pending states
9834 			if (is(t.disabled)) {
9835 				b = t.disabled;
9836 				t.disabled = -1;
9837 				t.setDisabled(b);
9838 			}
9839 
9840 			if (is(t.active)) {
9841 				b = t.active;
9842 				t.active = -1;
9843 				t.setActive(b);
9844 			}
9845 		},
9846 
9847 		remove : function() {
9848 			DOM.remove(this.id);
9849 			this.destroy();
9850 		},
9851 
9852 		destroy : function() {
9853 			tinymce.dom.Event.clear(this.id);
9854 		}
9855 	});
9856 })(tinymce);
9857 tinymce.create('tinymce.ui.Container:tinymce.ui.Control', {
9858 	Container : function(id, s, editor) {
9859 		this.parent(id, s, editor);
9860 
9861 		this.controls = [];
9862 
9863 		this.lookup = {};
9864 	},
9865 
9866 	add : function(c) {
9867 		this.lookup[c.id] = c;
9868 		this.controls.push(c);
9869 
9870 		return c;
9871 	},
9872 
9873 	get : function(n) {
9874 		return this.lookup[n];
9875 	}
9876 });
9877 
9878 
9879 tinymce.create('tinymce.ui.Separator:tinymce.ui.Control', {
9880 	Separator : function(id, s) {
9881 		this.parent(id, s);
9882 		this.classPrefix = 'mceSeparator';
9883 		this.setDisabled(true);
9884 	},
9885 
9886 	renderHTML : function() {
9887 		return tinymce.DOM.createHTML('span', {'class' : this.classPrefix, role : 'separator', 'aria-orientation' : 'vertical', tabindex : '-1'});
9888 	}
9889 });
9890 
9891 (function(tinymce) {
9892 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9893 
9894 	tinymce.create('tinymce.ui.MenuItem:tinymce.ui.Control', {
9895 		MenuItem : function(id, s) {
9896 			this.parent(id, s);
9897 			this.classPrefix = 'mceMenuItem';
9898 		},
9899 
9900 		setSelected : function(s) {
9901 			this.setState('Selected', s);
9902 			this.setAriaProperty('checked', !!s);
9903 			this.selected = s;
9904 		},
9905 
9906 		isSelected : function() {
9907 			return this.selected;
9908 		},
9909 
9910 		postRender : function() {
9911 			var t = this;
9912 			
9913 			t.parent();
9914 
9915 			// Set pending state
9916 			if (is(t.selected))
9917 				t.setSelected(t.selected);
9918 		}
9919 	});
9920 })(tinymce);
9921 
9922 (function(tinymce) {
9923 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, walk = tinymce.walk;
9924 
9925 	tinymce.create('tinymce.ui.Menu:tinymce.ui.MenuItem', {
9926 		Menu : function(id, s) {
9927 			var t = this;
9928 
9929 			t.parent(id, s);
9930 			t.items = {};
9931 			t.collapsed = false;
9932 			t.menuCount = 0;
9933 			t.onAddItem = new tinymce.util.Dispatcher(this);
9934 		},
9935 
9936 		expand : function(d) {
9937 			var t = this;
9938 
9939 			if (d) {
9940 				walk(t, function(o) {
9941 					if (o.expand)
9942 						o.expand();
9943 				}, 'items', t);
9944 			}
9945 
9946 			t.collapsed = false;
9947 		},
9948 
9949 		collapse : function(d) {
9950 			var t = this;
9951 
9952 			if (d) {
9953 				walk(t, function(o) {
9954 					if (o.collapse)
9955 						o.collapse();
9956 				}, 'items', t);
9957 			}
9958 
9959 			t.collapsed = true;
9960 		},
9961 
9962 		isCollapsed : function() {
9963 			return this.collapsed;
9964 		},
9965 
9966 		add : function(o) {
9967 			if (!o.settings)
9968 				o = new tinymce.ui.MenuItem(o.id || DOM.uniqueId(), o);
9969 
9970 			this.onAddItem.dispatch(this, o);
9971 
9972 			return this.items[o.id] = o;
9973 		},
9974 
9975 		addSeparator : function() {
9976 			return this.add({separator : true});
9977 		},
9978 
9979 		addMenu : function(o) {
9980 			if (!o.collapse)
9981 				o = this.createMenu(o);
9982 
9983 			this.menuCount++;
9984 
9985 			return this.add(o);
9986 		},
9987 
9988 		hasMenus : function() {
9989 			return this.menuCount !== 0;
9990 		},
9991 
9992 		remove : function(o) {
9993 			delete this.items[o.id];
9994 		},
9995 
9996 		removeAll : function() {
9997 			var t = this;
9998 
9999 			walk(t, function(o) {
10000 				if (o.removeAll)
10001 					o.removeAll();
10002 				else
10003 					o.remove();
10004 
10005 				o.destroy();
10006 			}, 'items', t);
10007 
10008 			t.items = {};
10009 		},
10010 
10011 		createMenu : function(o) {
10012 			var m = new tinymce.ui.Menu(o.id || DOM.uniqueId(), o);
10013 
10014 			m.onAddItem.add(this.onAddItem.dispatch, this.onAddItem);
10015 
10016 			return m;
10017 		}
10018 	});
10019 })(tinymce);
10020 (function(tinymce) {
10021 	var is = tinymce.is, DOM = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event, Element = tinymce.dom.Element;
10022 
10023 	tinymce.create('tinymce.ui.DropMenu:tinymce.ui.Menu', {
10024 		DropMenu : function(id, s) {
10025 			s = s || {};
10026 			s.container = s.container || DOM.doc.body;
10027 			s.offset_x = s.offset_x || 0;
10028 			s.offset_y = s.offset_y || 0;
10029 			s.vp_offset_x = s.vp_offset_x || 0;
10030 			s.vp_offset_y = s.vp_offset_y || 0;
10031 
10032 			if (is(s.icons) && !s.icons)
10033 				s['class'] += ' mceNoIcons';
10034 
10035 			this.parent(id, s);
10036 			this.onShowMenu = new tinymce.util.Dispatcher(this);
10037 			this.onHideMenu = new tinymce.util.Dispatcher(this);
10038 			this.classPrefix = 'mceMenu';
10039 		},
10040 
10041 		createMenu : function(s) {
10042 			var t = this, cs = t.settings, m;
10043 
10044 			s.container = s.container || cs.container;
10045 			s.parent = t;
10046 			s.constrain = s.constrain || cs.constrain;
10047 			s['class'] = s['class'] || cs['class'];
10048 			s.vp_offset_x = s.vp_offset_x || cs.vp_offset_x;
10049 			s.vp_offset_y = s.vp_offset_y || cs.vp_offset_y;
10050 			s.keyboard_focus = cs.keyboard_focus;
10051 			m = new tinymce.ui.DropMenu(s.id || DOM.uniqueId(), s);
10052 
10053 			m.onAddItem.add(t.onAddItem.dispatch, t.onAddItem);
10054 
10055 			return m;
10056 		},
10057 		
10058 		focus : function() {
10059 			var t = this;
10060 			if (t.keyboardNav) {
10061 				t.keyboardNav.focus();
10062 			}
10063 		},
10064 
10065 		update : function() {
10066 			var t = this, s = t.settings, tb = DOM.get('menu_' + t.id + '_tbl'), co = DOM.get('menu_' + t.id + '_co'), tw, th;
10067 
10068 			tw = s.max_width ? Math.min(tb.offsetWidth, s.max_width) : tb.offsetWidth;
10069 			th = s.max_height ? Math.min(tb.offsetHeight, s.max_height) : tb.offsetHeight;
10070 
10071 			if (!DOM.boxModel)
10072 				t.element.setStyles({width : tw + 2, height : th + 2});
10073 			else
10074 				t.element.setStyles({width : tw, height : th});
10075 
10076 			if (s.max_width)
10077 				DOM.setStyle(co, 'width', tw);
10078 
10079 			if (s.max_height) {
10080 				DOM.setStyle(co, 'height', th);
10081 
10082 				if (tb.clientHeight < s.max_height)
10083 					DOM.setStyle(co, 'overflow', 'hidden');
10084 			}
10085 		},
10086 
10087 		showMenu : function(x, y, px) {
10088 			var t = this, s = t.settings, co, vp = DOM.getViewPort(), w, h, mx, my, ot = 2, dm, tb, cp = t.classPrefix;
10089 
10090 			t.collapse(1);
10091 
10092 			if (t.isMenuVisible)
10093 				return;
10094 
10095 			if (!t.rendered) {
10096 				co = DOM.add(t.settings.container, t.renderNode());
10097 
10098 				each(t.items, function(o) {
10099 					o.postRender();
10100 				});
10101 
10102 				t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10103 			} else
10104 				co = DOM.get('menu_' + t.id);
10105 
10106 			// Move layer out of sight unless it's Opera since it scrolls to top of page due to an bug
10107 			if (!tinymce.isOpera)
10108 				DOM.setStyles(co, {left : -0xFFFF , top : -0xFFFF});
10109 
10110 			DOM.show(co);
10111 			t.update();
10112 
10113 			x += s.offset_x || 0;
10114 			y += s.offset_y || 0;
10115 			vp.w -= 4;
10116 			vp.h -= 4;
10117 
10118 			// Move inside viewport if not submenu
10119 			if (s.constrain) {
10120 				w = co.clientWidth - ot;
10121 				h = co.clientHeight - ot;
10122 				mx = vp.x + vp.w;
10123 				my = vp.y + vp.h;
10124 
10125 				if ((x + s.vp_offset_x + w) > mx)
10126 					x = px ? px - w : Math.max(0, (mx - s.vp_offset_x) - w);
10127 
10128 				if ((y + s.vp_offset_y + h) > my)
10129 					y = Math.max(0, (my - s.vp_offset_y) - h);
10130 			}
10131 
10132 			DOM.setStyles(co, {left : x , top : y});
10133 			t.element.update();
10134 
10135 			t.isMenuVisible = 1;
10136 			t.mouseClickFunc = Event.add(co, 'click', function(e) {
10137 				var m;
10138 
10139 				e = e.target;
10140 
10141 				if (e && (e = DOM.getParent(e, 'tr')) && !DOM.hasClass(e, cp + 'ItemSub')) {
10142 					m = t.items[e.id];
10143 
10144 					if (m.isDisabled())
10145 						return;
10146 
10147 					dm = t;
10148 
10149 					while (dm) {
10150 						if (dm.hideMenu)
10151 							dm.hideMenu();
10152 
10153 						dm = dm.settings.parent;
10154 					}
10155 
10156 					if (m.settings.onclick)
10157 						m.settings.onclick(e);
10158 
10159 					return false; // Cancel to fix onbeforeunload problem
10160 				}
10161 			});
10162 
10163 			if (t.hasMenus()) {
10164 				t.mouseOverFunc = Event.add(co, 'mouseover', function(e) {
10165 					var m, r, mi;
10166 
10167 					e = e.target;
10168 					if (e && (e = DOM.getParent(e, 'tr'))) {
10169 						m = t.items[e.id];
10170 
10171 						if (t.lastMenu)
10172 							t.lastMenu.collapse(1);
10173 
10174 						if (m.isDisabled())
10175 							return;
10176 
10177 						if (e && DOM.hasClass(e, cp + 'ItemSub')) {
10178 							//p = DOM.getPos(s.container);
10179 							r = DOM.getRect(e);
10180 							m.showMenu((r.x + r.w - ot), r.y - ot, r.x);
10181 							t.lastMenu = m;
10182 							DOM.addClass(DOM.get(m.id).firstChild, cp + 'ItemActive');
10183 						}
10184 					}
10185 				});
10186 			}
10187 			
10188 			Event.add(co, 'keydown', t._keyHandler, t);
10189 
10190 			t.onShowMenu.dispatch(t);
10191 
10192 			if (s.keyboard_focus) { 
10193 				t._setupKeyboardNav(); 
10194 			}
10195 		},
10196 
10197 		hideMenu : function(c) {
10198 			var t = this, co = DOM.get('menu_' + t.id), e;
10199 
10200 			if (!t.isMenuVisible)
10201 				return;
10202 
10203 			if (t.keyboardNav) t.keyboardNav.destroy();
10204 			Event.remove(co, 'mouseover', t.mouseOverFunc);
10205 			Event.remove(co, 'click', t.mouseClickFunc);
10206 			Event.remove(co, 'keydown', t._keyHandler);
10207 			DOM.hide(co);
10208 			t.isMenuVisible = 0;
10209 
10210 			if (!c)
10211 				t.collapse(1);
10212 
10213 			if (t.element)
10214 				t.element.hide();
10215 
10216 			if (e = DOM.get(t.id))
10217 				DOM.removeClass(e.firstChild, t.classPrefix + 'ItemActive');
10218 
10219 			t.onHideMenu.dispatch(t);
10220 		},
10221 
10222 		add : function(o) {
10223 			var t = this, co;
10224 
10225 			o = t.parent(o);
10226 
10227 			if (t.isRendered && (co = DOM.get('menu_' + t.id)))
10228 				t._add(DOM.select('tbody', co)[0], o);
10229 
10230 			return o;
10231 		},
10232 
10233 		collapse : function(d) {
10234 			this.parent(d);
10235 			this.hideMenu(1);
10236 		},
10237 
10238 		remove : function(o) {
10239 			DOM.remove(o.id);
10240 			this.destroy();
10241 
10242 			return this.parent(o);
10243 		},
10244 
10245 		destroy : function() {
10246 			var t = this, co = DOM.get('menu_' + t.id);
10247 
10248 			if (t.keyboardNav) t.keyboardNav.destroy();
10249 			Event.remove(co, 'mouseover', t.mouseOverFunc);
10250 			Event.remove(DOM.select('a', co), 'focus', t.mouseOverFunc);
10251 			Event.remove(co, 'click', t.mouseClickFunc);
10252 			Event.remove(co, 'keydown', t._keyHandler);
10253 
10254 			if (t.element)
10255 				t.element.remove();
10256 
10257 			DOM.remove(co);
10258 		},
10259 
10260 		renderNode : function() {
10261 			var t = this, s = t.settings, n, tb, co, w;
10262 
10263 			w = DOM.create('div', {role: 'listbox', id : 'menu_' + t.id, 'class' : s['class'], 'style' : 'position:absolute;left:0;top:0;z-index:200000;outline:0'});
10264 			if (t.settings.parent) {
10265 				DOM.setAttrib(w, 'aria-parent', 'menu_' + t.settings.parent.id);
10266 			}
10267 			co = DOM.add(w, 'div', {role: 'presentation', id : 'menu_' + t.id + '_co', 'class' : t.classPrefix + (s['class'] ? ' ' + s['class'] : '')});
10268 			t.element = new Element('menu_' + t.id, {blocker : 1, container : s.container});
10269 
10270 			if (s.menu_line)
10271 				DOM.add(co, 'span', {'class' : t.classPrefix + 'Line'});
10272 
10273 //			n = DOM.add(co, 'div', {id : 'menu_' + t.id + '_co', 'class' : 'mceMenuContainer'});
10274 			n = DOM.add(co, 'table', {role: 'presentation', id : 'menu_' + t.id + '_tbl', border : 0, cellPadding : 0, cellSpacing : 0});
10275 			tb = DOM.add(n, 'tbody');
10276 
10277 			each(t.items, function(o) {
10278 				t._add(tb, o);
10279 			});
10280 
10281 			t.rendered = true;
10282 
10283 			return w;
10284 		},
10285 
10286 		// Internal functions
10287 		_setupKeyboardNav : function(){
10288 			var contextMenu, menuItems, t=this; 
10289 			contextMenu = DOM.get('menu_' + t.id);
10290 			menuItems = DOM.select('a[role=option]', 'menu_' + t.id);
10291 			menuItems.splice(0,0,contextMenu);
10292 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
10293 				root: 'menu_' + t.id,
10294 				items: menuItems,
10295 				onCancel: function() {
10296 					t.hideMenu();
10297 				},
10298 				enableUpDown: true
10299 			});
10300 			contextMenu.focus();
10301 		},
10302 
10303 		_keyHandler : function(evt) {
10304 			var t = this, e;
10305 			switch (evt.keyCode) {
10306 				case 37: // Left
10307 					if (t.settings.parent) {
10308 						t.hideMenu();
10309 						t.settings.parent.focus();
10310 						Event.cancel(evt);
10311 					}
10312 					break;
10313 				case 39: // Right
10314 					if (t.mouseOverFunc)
10315 						t.mouseOverFunc(evt);
10316 					break;
10317 			}
10318 		},
10319 
10320 		_add : function(tb, o) {
10321 			var n, s = o.settings, a, ro, it, cp = this.classPrefix, ic;
10322 
10323 			if (s.separator) {
10324 				ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'ItemSeparator'});
10325 				DOM.add(ro, 'td', {'class' : cp + 'ItemSeparator'});
10326 
10327 				if (n = ro.previousSibling)
10328 					DOM.addClass(n, 'mceLast');
10329 
10330 				return;
10331 			}
10332 
10333 			n = ro = DOM.add(tb, 'tr', {id : o.id, 'class' : cp + 'Item ' + cp + 'ItemEnabled'});
10334 			n = it = DOM.add(n, s.titleItem ? 'th' : 'td');
10335 			n = a = DOM.add(n, 'a', {id: o.id + '_aria',  role: s.titleItem ? 'presentation' : 'option', href : 'javascript:;', onclick : "return false;", onmousedown : 'return false;'});
10336 
10337 			if (s.parent) {
10338 				DOM.setAttrib(a, 'aria-haspopup', 'true');
10339 				DOM.setAttrib(a, 'aria-owns', 'menu_' + o.id);
10340 			}
10341 
10342 			DOM.addClass(it, s['class']);
10343 //			n = DOM.add(n, 'span', {'class' : 'item'});
10344 
10345 			ic = DOM.add(n, 'span', {'class' : 'mceIcon' + (s.icon ? ' mce_' + s.icon : '')});
10346 
10347 			if (s.icon_src)
10348 				DOM.add(ic, 'img', {src : s.icon_src});
10349 
10350 			n = DOM.add(n, s.element || 'span', {'class' : 'mceText', title : o.settings.title}, o.settings.title);
10351 
10352 			if (o.settings.style) {
10353 				if (typeof o.settings.style == "function")
10354 					o.settings.style = o.settings.style();
10355 
10356 				DOM.setAttrib(n, 'style', o.settings.style);
10357 			}
10358 
10359 			if (tb.childNodes.length == 1)
10360 				DOM.addClass(ro, 'mceFirst');
10361 
10362 			if ((n = ro.previousSibling) && DOM.hasClass(n, cp + 'ItemSeparator'))
10363 				DOM.addClass(ro, 'mceFirst');
10364 
10365 			if (o.collapse)
10366 				DOM.addClass(ro, cp + 'ItemSub');
10367 
10368 			if (n = ro.previousSibling)
10369 				DOM.removeClass(n, 'mceLast');
10370 
10371 			DOM.addClass(ro, 'mceLast');
10372 		}
10373 	});
10374 })(tinymce);
10375 (function(tinymce) {
10376 	var DOM = tinymce.DOM;
10377 
10378 	tinymce.create('tinymce.ui.Button:tinymce.ui.Control', {
10379 		Button : function(id, s, ed) {
10380 			this.parent(id, s, ed);
10381 			this.classPrefix = 'mceButton';
10382 		},
10383 
10384 		renderHTML : function() {
10385 			var cp = this.classPrefix, s = this.settings, h, l;
10386 
10387 			l = DOM.encode(s.label || '');
10388 			h = '<a role="button" id="' + this.id + '" href="javascript:;" class="' + cp + ' ' + cp + 'Enabled ' + s['class'] + (l ? ' ' + cp + 'Labeled' : '') +'" onmousedown="return false;" onclick="return false;" aria-labelledby="' + this.id + '_voice" title="' + DOM.encode(s.title) + '">';
10389 			if (s.image && !(this.editor  &&this.editor.forcedHighContrastMode) )
10390 				h += '<span class="mceIcon ' + s['class'] + '"><img class="mceIcon" src="' + s.image + '" alt="' + DOM.encode(s.title) + '" /></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10391 			else
10392 				h += '<span class="mceIcon ' + s['class'] + '"></span>' + (l ? '<span class="' + cp + 'Label">' + l + '</span>' : '');
10393 
10394 			h += '<span class="mceVoiceLabel mceIconOnly" style="display: none;" id="' + this.id + '_voice">' + s.title + '</span>'; 
10395 			h += '</a>';
10396 			return h;
10397 		},
10398 
10399 		postRender : function() {
10400 			var t = this, s = t.settings, imgBookmark;
10401 
10402 			// In IE a large image that occupies the entire editor area will be deselected when a button is clicked, so
10403 			// need to keep the selection in case the selection is lost
10404 			if (tinymce.isIE && t.editor) {
10405 				tinymce.dom.Event.add(t.id, 'mousedown', function(e) {
10406 					var nodeName = t.editor.selection.getNode().nodeName;
10407 					imgBookmark = nodeName === 'IMG' ? t.editor.selection.getBookmark() : null;
10408 				});
10409 			}
10410 			tinymce.dom.Event.add(t.id, 'click', function(e) {
10411 				if (!t.isDisabled()) {
10412 					// restore the selection in case the selection is lost in IE
10413 					if (tinymce.isIE && t.editor && imgBookmark !== null) {
10414 						t.editor.selection.moveToBookmark(imgBookmark);
10415 					}
10416 					return s.onclick.call(s.scope, e);
10417 				}
10418 			});
10419 			tinymce.dom.Event.add(t.id, 'keyup', function(e) {
10420 				if (!t.isDisabled() && e.keyCode==tinymce.VK.SPACEBAR)
10421 					return s.onclick.call(s.scope, e);
10422 			});
10423 		}
10424 	});
10425 })(tinymce);
10426 
10427 (function(tinymce) {
10428 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
10429 
10430 	tinymce.create('tinymce.ui.ListBox:tinymce.ui.Control', {
10431 		ListBox : function(id, s, ed) {
10432 			var t = this;
10433 
10434 			t.parent(id, s, ed);
10435 
10436 			t.items = [];
10437 
10438 			t.onChange = new Dispatcher(t);
10439 
10440 			t.onPostRender = new Dispatcher(t);
10441 
10442 			t.onAdd = new Dispatcher(t);
10443 
10444 			t.onRenderMenu = new tinymce.util.Dispatcher(this);
10445 
10446 			t.classPrefix = 'mceListBox';
10447 			t.marked = {};
10448 		},
10449 
10450 		select : function(va) {
10451 			var t = this, fv, f;
10452 
10453 			t.marked = {};
10454 
10455 			if (va == undef)
10456 				return t.selectByIndex(-1);
10457 
10458 			// Is string or number make function selector
10459 			if (va && typeof(va)=="function")
10460 				f = va;
10461 			else {
10462 				f = function(v) {
10463 					return v == va;
10464 				};
10465 			}
10466 
10467 			// Do we need to do something?
10468 			if (va != t.selectedValue) {
10469 				// Find item
10470 				each(t.items, function(o, i) {
10471 					if (f(o.value)) {
10472 						fv = 1;
10473 						t.selectByIndex(i);
10474 						return false;
10475 					}
10476 				});
10477 
10478 				if (!fv)
10479 					t.selectByIndex(-1);
10480 			}
10481 		},
10482 
10483 		selectByIndex : function(idx) {
10484 			var t = this, e, o, label;
10485 
10486 			t.marked = {};
10487 
10488 			if (idx != t.selectedIndex) {
10489 				e = DOM.get(t.id + '_text');
10490 				label = DOM.get(t.id + '_voiceDesc');
10491 				o = t.items[idx];
10492 
10493 				if (o) {
10494 					t.selectedValue = o.value;
10495 					t.selectedIndex = idx;
10496 					DOM.setHTML(e, DOM.encode(o.title));
10497 					DOM.setHTML(label, t.settings.title + " - " + o.title);
10498 					DOM.removeClass(e, 'mceTitle');
10499 					DOM.setAttrib(t.id, 'aria-valuenow', o.title);
10500 				} else {
10501 					DOM.setHTML(e, DOM.encode(t.settings.title));
10502 					DOM.setHTML(label, DOM.encode(t.settings.title));
10503 					DOM.addClass(e, 'mceTitle');
10504 					t.selectedValue = t.selectedIndex = null;
10505 					DOM.setAttrib(t.id, 'aria-valuenow', t.settings.title);
10506 				}
10507 				e = 0;
10508 			}
10509 		},
10510 
10511 		mark : function(value) {
10512 			this.marked[value] = true;
10513 		},
10514 
10515 		add : function(n, v, o) {
10516 			var t = this;
10517 
10518 			o = o || {};
10519 			o = tinymce.extend(o, {
10520 				title : n,
10521 				value : v
10522 			});
10523 
10524 			t.items.push(o);
10525 			t.onAdd.dispatch(t, o);
10526 		},
10527 
10528 		getLength : function() {
10529 			return this.items.length;
10530 		},
10531 
10532 		renderHTML : function() {
10533 			var h = '', t = this, s = t.settings, cp = t.classPrefix;
10534 
10535 			h = '<span role="listbox" aria-haspopup="true" aria-labelledby="' + t.id +'_voiceDesc" aria-describedby="' + t.id + '_voiceDesc"><table role="presentation" tabindex="0" id="' + t.id + '" cellpadding="0" cellspacing="0" class="' + cp + ' ' + cp + 'Enabled' + (s['class'] ? (' ' + s['class']) : '') + '"><tbody><tr>';
10536 			h += '<td>' + DOM.createHTML('span', {id: t.id + '_voiceDesc', 'class': 'voiceLabel', style:'display:none;'}, t.settings.title); 
10537 			h += DOM.createHTML('a', {id : t.id + '_text', tabindex : -1, href : 'javascript:;', 'class' : 'mceText', onclick : "return false;", onmousedown : 'return false;'}, DOM.encode(t.settings.title)) + '</td>';
10538 			h += '<td>' + DOM.createHTML('a', {id : t.id + '_open', tabindex : -1, href : 'javascript:;', 'class' : 'mceOpen', onclick : "return false;", onmousedown : 'return false;'}, '<span><span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span></span>') + '</td>';
10539 			h += '</tr></tbody></table></span>';
10540 
10541 			return h;
10542 		},
10543 
10544 		showMenu : function() {
10545 			var t = this, p2, e = DOM.get(this.id), m;
10546 
10547 			if (t.isDisabled() || t.items.length === 0)
10548 				return;
10549 
10550 			if (t.menu && t.menu.isMenuVisible)
10551 				return t.hideMenu();
10552 
10553 			if (!t.isMenuRendered) {
10554 				t.renderMenu();
10555 				t.isMenuRendered = true;
10556 			}
10557 
10558 			p2 = DOM.getPos(e);
10559 
10560 			m = t.menu;
10561 			m.settings.offset_x = p2.x;
10562 			m.settings.offset_y = p2.y;
10563 			m.settings.keyboard_focus = !tinymce.isOpera; // Opera is buggy when it comes to auto focus
10564 
10565 			// Select in menu
10566 			each(t.items, function(o) {
10567 				if (m.items[o.id]) {
10568 					m.items[o.id].setSelected(0);
10569 				}
10570 			});
10571 
10572 			each(t.items, function(o) {
10573 				if (m.items[o.id] && t.marked[o.value]) {
10574 					m.items[o.id].setSelected(1);
10575 				}
10576 
10577 				if (o.value === t.selectedValue) {
10578 					m.items[o.id].setSelected(1);
10579 				}
10580 			});
10581 
10582 			m.showMenu(0, e.clientHeight);
10583 
10584 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10585 			DOM.addClass(t.id, t.classPrefix + 'Selected');
10586 
10587 			//DOM.get(t.id + '_text').focus();
10588 		},
10589 
10590 		hideMenu : function(e) {
10591 			var t = this;
10592 
10593 			if (t.menu && t.menu.isMenuVisible) {
10594 				DOM.removeClass(t.id, t.classPrefix + 'Selected');
10595 
10596 				// Prevent double toogles by canceling the mouse click event to the button
10597 				if (e && e.type == "mousedown" && (e.target.id == t.id + '_text' || e.target.id == t.id + '_open'))
10598 					return;
10599 
10600 				if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10601 					DOM.removeClass(t.id, t.classPrefix + 'Selected');
10602 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10603 					t.menu.hideMenu();
10604 				}
10605 			}
10606 		},
10607 
10608 		renderMenu : function() {
10609 			var t = this, m;
10610 
10611 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10612 				menu_line : 1,
10613 				'class' : t.classPrefix + 'Menu mceNoIcons',
10614 				max_width : 250,
10615 				max_height : 150
10616 			});
10617 
10618 			m.onHideMenu.add(function() {
10619 				t.hideMenu();
10620 				t.focus();
10621 			});
10622 
10623 			m.add({
10624 				title : t.settings.title,
10625 				'class' : 'mceMenuItemTitle',
10626 				onclick : function() {
10627 					if (t.settings.onselect('') !== false)
10628 						t.select(''); // Must be runned after
10629 				}
10630 			});
10631 
10632 			each(t.items, function(o) {
10633 				// No value then treat it as a title
10634 				if (o.value === undef) {
10635 					m.add({
10636 						title : o.title,
10637 						role : "option",
10638 						'class' : 'mceMenuItemTitle',
10639 						onclick : function() {
10640 							if (t.settings.onselect('') !== false)
10641 								t.select(''); // Must be runned after
10642 						}
10643 					});
10644 				} else {
10645 					o.id = DOM.uniqueId();
10646 					o.role= "option";
10647 					o.onclick = function() {
10648 						if (t.settings.onselect(o.value) !== false)
10649 							t.select(o.value); // Must be runned after
10650 					};
10651 
10652 					m.add(o);
10653 				}
10654 			});
10655 
10656 			t.onRenderMenu.dispatch(t, m);
10657 			t.menu = m;
10658 		},
10659 
10660 		postRender : function() {
10661 			var t = this, cp = t.classPrefix;
10662 
10663 			Event.add(t.id, 'click', t.showMenu, t);
10664 			Event.add(t.id, 'keydown', function(evt) {
10665 				if (evt.keyCode == 32) { // Space
10666 					t.showMenu(evt);
10667 					Event.cancel(evt);
10668 				}
10669 			});
10670 			Event.add(t.id, 'focus', function() {
10671 				if (!t._focused) {
10672 					t.keyDownHandler = Event.add(t.id, 'keydown', function(e) {
10673 						if (e.keyCode == 40) {
10674 							t.showMenu();
10675 							Event.cancel(e);
10676 						}
10677 					});
10678 					t.keyPressHandler = Event.add(t.id, 'keypress', function(e) {
10679 						var v;
10680 						if (e.keyCode == 13) {
10681 							// Fake select on enter
10682 							v = t.selectedValue;
10683 							t.selectedValue = null; // Needs to be null to fake change
10684 							Event.cancel(e);
10685 							t.settings.onselect(v);
10686 						}
10687 					});
10688 				}
10689 
10690 				t._focused = 1;
10691 			});
10692 			Event.add(t.id, 'blur', function() {
10693 				Event.remove(t.id, 'keydown', t.keyDownHandler);
10694 				Event.remove(t.id, 'keypress', t.keyPressHandler);
10695 				t._focused = 0;
10696 			});
10697 
10698 			// Old IE doesn't have hover on all elements
10699 			if (tinymce.isIE6 || !DOM.boxModel) {
10700 				Event.add(t.id, 'mouseover', function() {
10701 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
10702 						DOM.addClass(t.id, cp + 'Hover');
10703 				});
10704 
10705 				Event.add(t.id, 'mouseout', function() {
10706 					if (!DOM.hasClass(t.id, cp + 'Disabled'))
10707 						DOM.removeClass(t.id, cp + 'Hover');
10708 				});
10709 			}
10710 
10711 			t.onPostRender.dispatch(t, DOM.get(t.id));
10712 		},
10713 
10714 		destroy : function() {
10715 			this.parent();
10716 
10717 			Event.clear(this.id + '_text');
10718 			Event.clear(this.id + '_open');
10719 		}
10720 	});
10721 })(tinymce);
10722 
10723 (function(tinymce) {
10724 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, Dispatcher = tinymce.util.Dispatcher, undef;
10725 
10726 	tinymce.create('tinymce.ui.NativeListBox:tinymce.ui.ListBox', {
10727 		NativeListBox : function(id, s) {
10728 			this.parent(id, s);
10729 			this.classPrefix = 'mceNativeListBox';
10730 		},
10731 
10732 		setDisabled : function(s) {
10733 			DOM.get(this.id).disabled = s;
10734 			this.setAriaProperty('disabled', s);
10735 		},
10736 
10737 		isDisabled : function() {
10738 			return DOM.get(this.id).disabled;
10739 		},
10740 
10741 		select : function(va) {
10742 			var t = this, fv, f;
10743 
10744 			if (va == undef)
10745 				return t.selectByIndex(-1);
10746 
10747 			// Is string or number make function selector
10748 			if (va && typeof(va)=="function")
10749 				f = va;
10750 			else {
10751 				f = function(v) {
10752 					return v == va;
10753 				};
10754 			}
10755 
10756 			// Do we need to do something?
10757 			if (va != t.selectedValue) {
10758 				// Find item
10759 				each(t.items, function(o, i) {
10760 					if (f(o.value)) {
10761 						fv = 1;
10762 						t.selectByIndex(i);
10763 						return false;
10764 					}
10765 				});
10766 
10767 				if (!fv)
10768 					t.selectByIndex(-1);
10769 			}
10770 		},
10771 
10772 		selectByIndex : function(idx) {
10773 			DOM.get(this.id).selectedIndex = idx + 1;
10774 			this.selectedValue = this.items[idx] ? this.items[idx].value : null;
10775 		},
10776 
10777 		add : function(n, v, a) {
10778 			var o, t = this;
10779 
10780 			a = a || {};
10781 			a.value = v;
10782 
10783 			if (t.isRendered())
10784 				DOM.add(DOM.get(this.id), 'option', a, n);
10785 
10786 			o = {
10787 				title : n,
10788 				value : v,
10789 				attribs : a
10790 			};
10791 
10792 			t.items.push(o);
10793 			t.onAdd.dispatch(t, o);
10794 		},
10795 
10796 		getLength : function() {
10797 			return this.items.length;
10798 		},
10799 
10800 		renderHTML : function() {
10801 			var h, t = this;
10802 
10803 			h = DOM.createHTML('option', {value : ''}, '-- ' + t.settings.title + ' --');
10804 
10805 			each(t.items, function(it) {
10806 				h += DOM.createHTML('option', {value : it.value}, it.title);
10807 			});
10808 
10809 			h = DOM.createHTML('select', {id : t.id, 'class' : 'mceNativeListBox', 'aria-labelledby': t.id + '_aria'}, h);
10810 			h += DOM.createHTML('span', {id : t.id + '_aria', 'style': 'display: none'}, t.settings.title);
10811 			return h;
10812 		},
10813 
10814 		postRender : function() {
10815 			var t = this, ch, changeListenerAdded = true;
10816 
10817 			t.rendered = true;
10818 
10819 			function onChange(e) {
10820 				var v = t.items[e.target.selectedIndex - 1];
10821 
10822 				if (v && (v = v.value)) {
10823 					t.onChange.dispatch(t, v);
10824 
10825 					if (t.settings.onselect)
10826 						t.settings.onselect(v);
10827 				}
10828 			};
10829 
10830 			Event.add(t.id, 'change', onChange);
10831 
10832 			// Accessibility keyhandler
10833 			Event.add(t.id, 'keydown', function(e) {
10834 				var bf;
10835 
10836 				Event.remove(t.id, 'change', ch);
10837 				changeListenerAdded = false;
10838 
10839 				bf = Event.add(t.id, 'blur', function() {
10840 					if (changeListenerAdded) return;
10841 					changeListenerAdded = true;
10842 					Event.add(t.id, 'change', onChange);
10843 					Event.remove(t.id, 'blur', bf);
10844 				});
10845 
10846 				//prevent default left and right keys on chrome - so that the keyboard navigation is used.
10847 				if (tinymce.isWebKit && (e.keyCode==37 ||e.keyCode==39)) {
10848 					return Event.prevent(e);
10849 				}
10850 				
10851 				if (e.keyCode == 13 || e.keyCode == 32) {
10852 					onChange(e);
10853 					return Event.cancel(e);
10854 				}
10855 			});
10856 
10857 			t.onPostRender.dispatch(t, DOM.get(t.id));
10858 		}
10859 	});
10860 })(tinymce);
10861 
10862 (function(tinymce) {
10863 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10864 
10865 	tinymce.create('tinymce.ui.MenuButton:tinymce.ui.Button', {
10866 		MenuButton : function(id, s, ed) {
10867 			this.parent(id, s, ed);
10868 
10869 			this.onRenderMenu = new tinymce.util.Dispatcher(this);
10870 
10871 			s.menu_container = s.menu_container || DOM.doc.body;
10872 		},
10873 
10874 		showMenu : function() {
10875 			var t = this, p1, p2, e = DOM.get(t.id), m;
10876 
10877 			if (t.isDisabled())
10878 				return;
10879 
10880 			if (!t.isMenuRendered) {
10881 				t.renderMenu();
10882 				t.isMenuRendered = true;
10883 			}
10884 
10885 			if (t.isMenuVisible)
10886 				return t.hideMenu();
10887 
10888 			p1 = DOM.getPos(t.settings.menu_container);
10889 			p2 = DOM.getPos(e);
10890 
10891 			m = t.menu;
10892 			m.settings.offset_x = p2.x;
10893 			m.settings.offset_y = p2.y;
10894 			m.settings.vp_offset_x = p2.x;
10895 			m.settings.vp_offset_y = p2.y;
10896 			m.settings.keyboard_focus = t._focused;
10897 			m.showMenu(0, e.firstChild.clientHeight);
10898 
10899 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
10900 			t.setState('Selected', 1);
10901 
10902 			t.isMenuVisible = 1;
10903 		},
10904 
10905 		renderMenu : function() {
10906 			var t = this, m;
10907 
10908 			m = t.settings.control_manager.createDropMenu(t.id + '_menu', {
10909 				menu_line : 1,
10910 				'class' : this.classPrefix + 'Menu',
10911 				icons : t.settings.icons
10912 			});
10913 
10914 			m.onHideMenu.add(function() {
10915 				t.hideMenu();
10916 				t.focus();
10917 			});
10918 
10919 			t.onRenderMenu.dispatch(t, m);
10920 			t.menu = m;
10921 		},
10922 
10923 		hideMenu : function(e) {
10924 			var t = this;
10925 
10926 			// Prevent double toogles by canceling the mouse click event to the button
10927 			if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id || e.id === t.id + '_open';}))
10928 				return;
10929 
10930 			if (!e || !DOM.getParent(e.target, '.mceMenu')) {
10931 				t.setState('Selected', 0);
10932 				Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
10933 				if (t.menu)
10934 					t.menu.hideMenu();
10935 			}
10936 
10937 			t.isMenuVisible = 0;
10938 		},
10939 
10940 		postRender : function() {
10941 			var t = this, s = t.settings;
10942 
10943 			Event.add(t.id, 'click', function() {
10944 				if (!t.isDisabled()) {
10945 					if (s.onclick)
10946 						s.onclick(t.value);
10947 
10948 					t.showMenu();
10949 				}
10950 			});
10951 		}
10952 	});
10953 })(tinymce);
10954 
10955 (function(tinymce) {
10956 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each;
10957 
10958 	tinymce.create('tinymce.ui.SplitButton:tinymce.ui.MenuButton', {
10959 		SplitButton : function(id, s, ed) {
10960 			this.parent(id, s, ed);
10961 			this.classPrefix = 'mceSplitButton';
10962 		},
10963 
10964 		renderHTML : function() {
10965 			var h, t = this, s = t.settings, h1;
10966 
10967 			h = '<tbody><tr>';
10968 
10969 			if (s.image)
10970 				h1 = DOM.createHTML('img ', {src : s.image, role: 'presentation', 'class' : 'mceAction ' + s['class']});
10971 			else
10972 				h1 = DOM.createHTML('span', {'class' : 'mceAction ' + s['class']}, '');
10973 
10974 			h1 += DOM.createHTML('span', {'class': 'mceVoiceLabel mceIconOnly', id: t.id + '_voice', style: 'display:none;'}, s.title);
10975 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_action', tabindex: '-1', href : 'javascript:;', 'class' : 'mceAction ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
10976 	
10977 			h1 = DOM.createHTML('span', {'class' : 'mceOpen ' + s['class']}, '<span style="display:none;" class="mceIconOnly" aria-hidden="true">\u25BC</span>');
10978 			h += '<td >' + DOM.createHTML('a', {role: 'button', id : t.id + '_open', tabindex: '-1', href : 'javascript:;', 'class' : 'mceOpen ' + s['class'], onclick : "return false;", onmousedown : 'return false;', title : s.title}, h1) + '</td>';
10979 
10980 			h += '</tr></tbody>';
10981 			h = DOM.createHTML('table', { role: 'presentation',   'class' : 'mceSplitButton mceSplitButtonEnabled ' + s['class'], cellpadding : '0', cellspacing : '0', title : s.title}, h);
10982 			return DOM.createHTML('div', {id : t.id, role: 'button', tabindex: '0', 'aria-labelledby': t.id + '_voice', 'aria-haspopup': 'true'}, h);
10983 		},
10984 
10985 		postRender : function() {
10986 			var t = this, s = t.settings, activate;
10987 
10988 			if (s.onclick) {
10989 				activate = function(evt) {
10990 					if (!t.isDisabled()) {
10991 						s.onclick(t.value);
10992 						Event.cancel(evt);
10993 					}
10994 				};
10995 				Event.add(t.id + '_action', 'click', activate);
10996 				Event.add(t.id, ['click', 'keydown'], function(evt) {
10997 					var DOM_VK_SPACE = 32, DOM_VK_ENTER = 14, DOM_VK_RETURN = 13, DOM_VK_UP = 38, DOM_VK_DOWN = 40;
10998 					if ((evt.keyCode === 32 || evt.keyCode === 13 || evt.keyCode === 14) && !evt.altKey && !evt.ctrlKey && !evt.metaKey) {
10999 						activate();
11000 						Event.cancel(evt);
11001 					} else if (evt.type === 'click' || evt.keyCode === DOM_VK_DOWN) {
11002 						t.showMenu();
11003 						Event.cancel(evt);
11004 					}
11005 				});
11006 			}
11007 
11008 			Event.add(t.id + '_open', 'click', function (evt) {
11009 				t.showMenu();
11010 				Event.cancel(evt);
11011 			});
11012 			Event.add([t.id, t.id + '_open'], 'focus', function() {t._focused = 1;});
11013 			Event.add([t.id, t.id + '_open'], 'blur', function() {t._focused = 0;});
11014 
11015 			// Old IE doesn't have hover on all elements
11016 			if (tinymce.isIE6 || !DOM.boxModel) {
11017 				Event.add(t.id, 'mouseover', function() {
11018 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11019 						DOM.addClass(t.id, 'mceSplitButtonHover');
11020 				});
11021 
11022 				Event.add(t.id, 'mouseout', function() {
11023 					if (!DOM.hasClass(t.id, 'mceSplitButtonDisabled'))
11024 						DOM.removeClass(t.id, 'mceSplitButtonHover');
11025 				});
11026 			}
11027 		},
11028 
11029 		destroy : function() {
11030 			this.parent();
11031 
11032 			Event.clear(this.id + '_action');
11033 			Event.clear(this.id + '_open');
11034 			Event.clear(this.id);
11035 		}
11036 	});
11037 })(tinymce);
11038 
11039 (function(tinymce) {
11040 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, is = tinymce.is, each = tinymce.each;
11041 
11042 	tinymce.create('tinymce.ui.ColorSplitButton:tinymce.ui.SplitButton', {
11043 		ColorSplitButton : function(id, s, ed) {
11044 			var t = this;
11045 
11046 			t.parent(id, s, ed);
11047 
11048 			t.settings = s = tinymce.extend({
11049 				colors : '000000,993300,333300,003300,003366,000080,333399,333333,800000,FF6600,808000,008000,008080,0000FF,666699,808080,FF0000,FF9900,99CC00,339966,33CCCC,3366FF,800080,999999,FF00FF,FFCC00,FFFF00,00FF00,00FFFF,00CCFF,993366,C0C0C0,FF99CC,FFCC99,FFFF99,CCFFCC,CCFFFF,99CCFF,CC99FF,FFFFFF',
11050 				grid_width : 8,
11051 				default_color : '#888888'
11052 			}, t.settings);
11053 
11054 			t.onShowMenu = new tinymce.util.Dispatcher(t);
11055 
11056 			t.onHideMenu = new tinymce.util.Dispatcher(t);
11057 
11058 			t.value = s.default_color;
11059 		},
11060 
11061 		showMenu : function() {
11062 			var t = this, r, p, e, p2;
11063 
11064 			if (t.isDisabled())
11065 				return;
11066 
11067 			if (!t.isMenuRendered) {
11068 				t.renderMenu();
11069 				t.isMenuRendered = true;
11070 			}
11071 
11072 			if (t.isMenuVisible)
11073 				return t.hideMenu();
11074 
11075 			e = DOM.get(t.id);
11076 			DOM.show(t.id + '_menu');
11077 			DOM.addClass(e, 'mceSplitButtonSelected');
11078 			p2 = DOM.getPos(e);
11079 			DOM.setStyles(t.id + '_menu', {
11080 				left : p2.x,
11081 				top : p2.y + e.firstChild.clientHeight,
11082 				zIndex : 200000
11083 			});
11084 			e = 0;
11085 
11086 			Event.add(DOM.doc, 'mousedown', t.hideMenu, t);
11087 			t.onShowMenu.dispatch(t);
11088 
11089 			if (t._focused) {
11090 				t._keyHandler = Event.add(t.id + '_menu', 'keydown', function(e) {
11091 					if (e.keyCode == 27)
11092 						t.hideMenu();
11093 				});
11094 
11095 				DOM.select('a', t.id + '_menu')[0].focus(); // Select first link
11096 			}
11097 
11098 			t.keyboardNav = new tinymce.ui.KeyboardNavigation({
11099 				root: t.id + '_menu',
11100 				items: DOM.select('a', t.id + '_menu'),
11101 				onCancel: function() {
11102 					t.hideMenu();
11103 					t.focus();
11104 				}
11105 			});
11106 
11107 			t.keyboardNav.focus();
11108 			t.isMenuVisible = 1;
11109 		},
11110 
11111 		hideMenu : function(e) {
11112 			var t = this;
11113 
11114 			if (t.isMenuVisible) {
11115 				// Prevent double toogles by canceling the mouse click event to the button
11116 				if (e && e.type == "mousedown" && DOM.getParent(e.target, function(e) {return e.id === t.id + '_open';}))
11117 					return;
11118 
11119 				if (!e || !DOM.getParent(e.target, '.mceSplitButtonMenu')) {
11120 					DOM.removeClass(t.id, 'mceSplitButtonSelected');
11121 					Event.remove(DOM.doc, 'mousedown', t.hideMenu, t);
11122 					Event.remove(t.id + '_menu', 'keydown', t._keyHandler);
11123 					DOM.hide(t.id + '_menu');
11124 				}
11125 
11126 				t.isMenuVisible = 0;
11127 				t.onHideMenu.dispatch();
11128 				t.keyboardNav.destroy();
11129 			}
11130 		},
11131 
11132 		renderMenu : function() {
11133 			var t = this, m, i = 0, s = t.settings, n, tb, tr, w, context;
11134 
11135 			w = DOM.add(s.menu_container, 'div', {role: 'listbox', id : t.id + '_menu', 'class' : s.menu_class + ' ' + s['class'], style : 'position:absolute;left:0;top:-1000px;'});
11136 			m = DOM.add(w, 'div', {'class' : s['class'] + ' mceSplitButtonMenu'});
11137 			DOM.add(m, 'span', {'class' : 'mceMenuLine'});
11138 
11139 			n = DOM.add(m, 'table', {role: 'presentation', 'class' : 'mceColorSplitMenu'});
11140 			tb = DOM.add(n, 'tbody');
11141 
11142 			// Generate color grid
11143 			i = 0;
11144 			each(is(s.colors, 'array') ? s.colors : s.colors.split(','), function(c) {
11145 				c = c.replace(/^#/, '');
11146 
11147 				if (!i--) {
11148 					tr = DOM.add(tb, 'tr');
11149 					i = s.grid_width - 1;
11150 				}
11151 
11152 				n = DOM.add(tr, 'td');
11153 				var settings = {
11154 					href : 'javascript:;',
11155 					style : {
11156 						backgroundColor : '#' + c
11157 					},
11158 					'title': t.editor.getLang('colors.' + c, c),
11159 					'data-mce-color' : '#' + c
11160 				};
11161 
11162 				// adding a proper ARIA role = button causes JAWS to read things incorrectly on IE.
11163 				if (!tinymce.isIE ) {
11164 					settings.role = 'option';
11165 				}
11166 
11167 				n = DOM.add(n, 'a', settings);
11168 
11169 				if (t.editor.forcedHighContrastMode) {
11170 					n = DOM.add(n, 'canvas', { width: 16, height: 16, 'aria-hidden': 'true' });
11171 					if (n.getContext && (context = n.getContext("2d"))) {
11172 						context.fillStyle = '#' + c;
11173 						context.fillRect(0, 0, 16, 16);
11174 					} else {
11175 						// No point leaving a canvas element around if it's not supported for drawing on anyway.
11176 						DOM.remove(n);
11177 					}
11178 				}
11179 			});
11180 
11181 			if (s.more_colors_func) {
11182 				n = DOM.add(tb, 'tr');
11183 				n = DOM.add(n, 'td', {colspan : s.grid_width, 'class' : 'mceMoreColors'});
11184 				n = DOM.add(n, 'a', {role: 'option', id : t.id + '_more', href : 'javascript:;', onclick : 'return false;', 'class' : 'mceMoreColors'}, s.more_colors_title);
11185 
11186 				Event.add(n, 'click', function(e) {
11187 					s.more_colors_func.call(s.more_colors_scope || this);
11188 					return Event.cancel(e); // Cancel to fix onbeforeunload problem
11189 				});
11190 			}
11191 
11192 			DOM.addClass(m, 'mceColorSplitMenu');
11193 
11194 			// Prevent IE from scrolling and hindering click to occur #4019
11195 			Event.add(t.id + '_menu', 'mousedown', function(e) {return Event.cancel(e);});
11196 
11197 			Event.add(t.id + '_menu', 'click', function(e) {
11198 				var c;
11199 
11200 				e = DOM.getParent(e.target, 'a', tb);
11201 
11202 				if (e && e.nodeName.toLowerCase() == 'a' && (c = e.getAttribute('data-mce-color')))
11203 					t.setColor(c);
11204 
11205 				return false; // Prevent IE auto save warning
11206 			});
11207 
11208 			return w;
11209 		},
11210 
11211 		setColor : function(c) {
11212 			this.displayColor(c);
11213 			this.hideMenu();
11214 			this.settings.onselect(c);
11215 		},
11216 		
11217 		displayColor : function(c) {
11218 			var t = this;
11219 
11220 			DOM.setStyle(t.id + '_preview', 'backgroundColor', c);
11221 
11222 			t.value = c;
11223 		},
11224 
11225 		postRender : function() {
11226 			var t = this, id = t.id;
11227 
11228 			t.parent();
11229 			DOM.add(id + '_action', 'div', {id : id + '_preview', 'class' : 'mceColorPreview'});
11230 			DOM.setStyle(t.id + '_preview', 'backgroundColor', t.value);
11231 		},
11232 
11233 		destroy : function() {
11234 			var self = this;
11235 
11236 			self.parent();
11237 
11238 			Event.clear(self.id + '_menu');
11239 			Event.clear(self.id + '_more');
11240 			DOM.remove(self.id + '_menu');
11241 
11242 			if (self.keyboardNav) {
11243 				self.keyboardNav.destroy();
11244 			}
11245 		}
11246 	});
11247 })(tinymce);
11248 
11249 (function(tinymce) {
11250 // Shorten class names
11251 var dom = tinymce.DOM, each = tinymce.each, Event = tinymce.dom.Event;
11252 tinymce.create('tinymce.ui.ToolbarGroup:tinymce.ui.Container', {
11253 	renderHTML : function() {
11254 		var t = this, h = [], controls = t.controls, each = tinymce.each, settings = t.settings;
11255 
11256 		h.push('<div id="' + t.id + '" role="group" aria-labelledby="' + t.id + '_voice">');
11257 		//TODO: ACC test this out - adding a role = application for getting the landmarks working well.
11258 		h.push("<span role='application'>");
11259 		h.push('<span id="' + t.id + '_voice" class="mceVoiceLabel" style="display:none;">' + dom.encode(settings.name) + '</span>');
11260 		each(controls, function(toolbar) {
11261 			h.push(toolbar.renderHTML());
11262 		});
11263 		h.push("</span>");
11264 		h.push('</div>');
11265 
11266 		return h.join('');
11267 	},
11268 	
11269 	focus : function() {
11270 		var t = this;
11271 		dom.get(t.id).focus();
11272 	},
11273 	
11274 	postRender : function() {
11275 		var t = this, items = [];
11276 
11277 		each(t.controls, function(toolbar) {
11278 			each (toolbar.controls, function(control) {
11279 				if (control.id) {
11280 					items.push(control);
11281 				}
11282 			});
11283 		});
11284 
11285 		t.keyNav = new tinymce.ui.KeyboardNavigation({
11286 			root: t.id,
11287 			items: items,
11288 			onCancel: function() {
11289 				//Move focus if webkit so that navigation back will read the item.
11290 				if (tinymce.isWebKit) {
11291 					dom.get(t.editor.id+"_ifr").focus();
11292 				}
11293 				t.editor.focus();
11294 			},
11295 			excludeFromTabOrder: !t.settings.tab_focus_toolbar
11296 		});
11297 	},
11298 	
11299 	destroy : function() {
11300 		var self = this;
11301 
11302 		self.parent();
11303 		self.keyNav.destroy();
11304 		Event.clear(self.id);
11305 	}
11306 });
11307 })(tinymce);
11308 
11309 (function(tinymce) {
11310 // Shorten class names
11311 var dom = tinymce.DOM, each = tinymce.each;
11312 tinymce.create('tinymce.ui.Toolbar:tinymce.ui.Container', {
11313 	renderHTML : function() {
11314 		var t = this, h = '', c, co, s = t.settings, i, pr, nx, cl;
11315 
11316 		cl = t.controls;
11317 		for (i=0; i<cl.length; i++) {
11318 			// Get current control, prev control, next control and if the control is a list box or not
11319 			co = cl[i];
11320 			pr = cl[i - 1];
11321 			nx = cl[i + 1];
11322 
11323 			// Add toolbar start
11324 			if (i === 0) {
11325 				c = 'mceToolbarStart';
11326 
11327 				if (co.Button)
11328 					c += ' mceToolbarStartButton';
11329 				else if (co.SplitButton)
11330 					c += ' mceToolbarStartSplitButton';
11331 				else if (co.ListBox)
11332 					c += ' mceToolbarStartListBox';
11333 
11334 				h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11335 			}
11336 
11337 			// Add toolbar end before list box and after the previous button
11338 			// This is to fix the o2k7 editor skins
11339 			if (pr && co.ListBox) {
11340 				if (pr.Button || pr.SplitButton)
11341 					h += dom.createHTML('td', {'class' : 'mceToolbarEnd'}, dom.createHTML('span', null, '<!-- IE -->'));
11342 			}
11343 
11344 			// Render control HTML
11345 
11346 			// IE 8 quick fix, needed to propertly generate a hit area for anchors
11347 			if (dom.stdMode)
11348 				h += '<td style="position: relative">' + co.renderHTML() + '</td>';
11349 			else
11350 				h += '<td>' + co.renderHTML() + '</td>';
11351 
11352 			// Add toolbar start after list box and before the next button
11353 			// This is to fix the o2k7 editor skins
11354 			if (nx && co.ListBox) {
11355 				if (nx.Button || nx.SplitButton)
11356 					h += dom.createHTML('td', {'class' : 'mceToolbarStart'}, dom.createHTML('span', null, '<!-- IE -->'));
11357 			}
11358 		}
11359 
11360 		c = 'mceToolbarEnd';
11361 
11362 		if (co.Button)
11363 			c += ' mceToolbarEndButton';
11364 		else if (co.SplitButton)
11365 			c += ' mceToolbarEndSplitButton';
11366 		else if (co.ListBox)
11367 			c += ' mceToolbarEndListBox';
11368 
11369 		h += dom.createHTML('td', {'class' : c}, dom.createHTML('span', null, '<!-- IE -->'));
11370 
11371 		return dom.createHTML('table', {id : t.id, 'class' : 'mceToolbar' + (s['class'] ? ' ' + s['class'] : ''), cellpadding : '0', cellspacing : '0', align : t.settings.align || '', role: 'presentation', tabindex: '-1'}, '<tbody><tr>' + h + '</tr></tbody>');
11372 	}
11373 });
11374 })(tinymce);
11375 
11376 (function(tinymce) {
11377 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each;
11378 
11379 	tinymce.create('tinymce.AddOnManager', {
11380 		AddOnManager : function() {
11381 			var self = this;
11382 
11383 			self.items = [];
11384 			self.urls = {};
11385 			self.lookup = {};
11386 			self.onAdd = new Dispatcher(self);
11387 		},
11388 
11389 		get : function(n) {
11390 			if (this.lookup[n]) {
11391 				return this.lookup[n].instance;
11392 			} else {
11393 				return undefined;
11394 			}
11395 		},
11396 
11397 		dependencies : function(n) {
11398 			var result;
11399 			if (this.lookup[n]) {
11400 				result = this.lookup[n].dependencies;
11401 			}
11402 			return result || [];
11403 		},
11404 
11405 		requireLangPack : function(n) {
11406 			var s = tinymce.settings;
11407 
11408 			if (s && s.language && s.language_load !== false)
11409 				tinymce.ScriptLoader.add(this.urls[n] + '/langs/' + s.language + '.js');
11410 		},
11411 
11412 		add : function(id, o, dependencies) {
11413 			this.items.push(o);
11414 			this.lookup[id] = {instance:o, dependencies:dependencies};
11415 			this.onAdd.dispatch(this, id, o);
11416 
11417 			return o;
11418 		},
11419 		createUrl: function(baseUrl, dep) {
11420 			if (typeof dep === "object") {
11421 				return dep
11422 			} else {
11423 				return {prefix: baseUrl.prefix, resource: dep, suffix: baseUrl.suffix};
11424 			}
11425 		},
11426 
11427 		addComponents: function(pluginName, scripts) {
11428 			var pluginUrl = this.urls[pluginName];
11429 			tinymce.each(scripts, function(script){
11430 				tinymce.ScriptLoader.add(pluginUrl+"/"+script);	
11431 			});
11432 		},
11433 
11434 		load : function(n, u, cb, s) {
11435 			var t = this, url = u;
11436 
11437 			function loadDependencies() {
11438 				var dependencies = t.dependencies(n);
11439 				tinymce.each(dependencies, function(dep) {
11440 					var newUrl = t.createUrl(u, dep);
11441 					t.load(newUrl.resource, newUrl, undefined, undefined);
11442 				});
11443 				if (cb) {
11444 					if (s) {
11445 						cb.call(s);
11446 					} else {
11447 						cb.call(tinymce.ScriptLoader);
11448 					}
11449 				}
11450 			}
11451 
11452 			if (t.urls[n])
11453 				return;
11454 			if (typeof u === "object")
11455 				url = u.prefix + u.resource + u.suffix;
11456 
11457 			if (url.indexOf('/') !== 0 && url.indexOf('://') == -1)
11458 				url = tinymce.baseURL + '/' + url;
11459 
11460 			t.urls[n] = url.substring(0, url.lastIndexOf('/'));
11461 
11462 			if (t.lookup[n]) {
11463 				loadDependencies();
11464 			} else {
11465 				tinymce.ScriptLoader.add(url, loadDependencies, s);
11466 			}
11467 		}
11468 	});
11469 
11470 	// Create plugin and theme managers
11471 	tinymce.PluginManager = new tinymce.AddOnManager();
11472 	tinymce.ThemeManager = new tinymce.AddOnManager();
11473 }(tinymce));
11474 
11475 (function(tinymce) {
11476 	// Shorten names
11477 	var each = tinymce.each, extend = tinymce.extend,
11478 		DOM = tinymce.DOM, Event = tinymce.dom.Event,
11479 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11480 		explode = tinymce.explode,
11481 		Dispatcher = tinymce.util.Dispatcher, undef, instanceCounter = 0;
11482 
11483 	// Setup some URLs where the editor API is located and where the document is
11484 	tinymce.documentBaseURL = window.location.href.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
11485 	if (!/[\/\\]$/.test(tinymce.documentBaseURL))
11486 		tinymce.documentBaseURL += '/';
11487 
11488 	tinymce.baseURL = new tinymce.util.URI(tinymce.documentBaseURL).toAbsolute(tinymce.baseURL);
11489 
11490 	tinymce.baseURI = new tinymce.util.URI(tinymce.baseURL);
11491 
11492 	// Add before unload listener
11493 	// This was required since IE was leaking memory if you added and removed beforeunload listeners
11494 	// with attachEvent/detatchEvent so this only adds one listener and instances can the attach to the onBeforeUnload event
11495 	tinymce.onBeforeUnload = new Dispatcher(tinymce);
11496 
11497 	// Must be on window or IE will leak if the editor is placed in frame or iframe
11498 	Event.add(window, 'beforeunload', function(e) {
11499 		tinymce.onBeforeUnload.dispatch(tinymce, e);
11500 	});
11501 
11502 	tinymce.onAddEditor = new Dispatcher(tinymce);
11503 
11504 	tinymce.onRemoveEditor = new Dispatcher(tinymce);
11505 
11506 	tinymce.EditorManager = extend(tinymce, {
11507 		editors : [],
11508 
11509 		i18n : {},
11510 
11511 		activeEditor : null,
11512 
11513 		init : function(s) {
11514 			var t = this, pl, sl = tinymce.ScriptLoader, e, el = [], ed;
11515 
11516 			function createId(elm) {
11517 				var id = elm.id;
11518 	
11519 				// Use element id, or unique name or generate a unique id
11520 				if (!id) {
11521 					id = elm.name;
11522 	
11523 					if (id && !DOM.get(id)) {
11524 						id = elm.name;
11525 					} else {
11526 						// Generate unique name
11527 						id = DOM.uniqueId();
11528 					}
11529 
11530 					elm.setAttribute('id', id);
11531 				}
11532 
11533 				return id;
11534 			};
11535 
11536 			function execCallback(se, n, s) {
11537 				var f = se[n];
11538 
11539 				if (!f)
11540 					return;
11541 
11542 				if (tinymce.is(f, 'string')) {
11543 					s = f.replace(/\.\w+$/, '');
11544 					s = s ? tinymce.resolve(s) : 0;
11545 					f = tinymce.resolve(f);
11546 				}
11547 
11548 				return f.apply(s || this, Array.prototype.slice.call(arguments, 2));
11549 			};
11550 
11551 			function hasClass(n, c) {
11552 				return c.constructor === RegExp ? c.test(n.className) : DOM.hasClass(n, c);
11553 			};
11554 
11555 			t.settings = s;
11556 
11557 			// Legacy call
11558 			Event.bind(window, 'ready', function() {
11559 				var l, co;
11560 
11561 				execCallback(s, 'onpageload');
11562 
11563 				switch (s.mode) {
11564 					case "exact":
11565 						l = s.elements || '';
11566 
11567 						if(l.length > 0) {
11568 							each(explode(l), function(v) {
11569 								if (DOM.get(v)) {
11570 									ed = new tinymce.Editor(v, s);
11571 									el.push(ed);
11572 									ed.render(1);
11573 								} else {
11574 									each(document.forms, function(f) {
11575 										each(f.elements, function(e) {
11576 											if (e.name === v) {
11577 												v = 'mce_editor_' + instanceCounter++;
11578 												DOM.setAttrib(e, 'id', v);
11579 
11580 												ed = new tinymce.Editor(v, s);
11581 												el.push(ed);
11582 												ed.render(1);
11583 											}
11584 										});
11585 									});
11586 								}
11587 							});
11588 						}
11589 						break;
11590 
11591 					case "textareas":
11592 					case "specific_textareas":
11593 						each(DOM.select('textarea'), function(elm) {
11594 							if (s.editor_deselector && hasClass(elm, s.editor_deselector))
11595 								return;
11596 
11597 							if (!s.editor_selector || hasClass(elm, s.editor_selector)) {
11598 								ed = new tinymce.Editor(createId(elm), s);
11599 								el.push(ed);
11600 								ed.render(1);
11601 							}
11602 						});
11603 						break;
11604 					
11605 					default:
11606 						if (s.types) {
11607 							// Process type specific selector
11608 							each(s.types, function(type) {
11609 								each(DOM.select(type.selector), function(elm) {
11610 									var editor = new tinymce.Editor(createId(elm), tinymce.extend({}, s, type));
11611 									el.push(editor);
11612 									editor.render(1);
11613 								});
11614 							});
11615 						} else if (s.selector) {
11616 							// Process global selector
11617 							each(DOM.select(s.selector), function(elm) {
11618 								var editor = new tinymce.Editor(createId(elm), s);
11619 								el.push(editor);
11620 								editor.render(1);
11621 							});
11622 						}
11623 				}
11624 
11625 				// Call onInit when all editors are initialized
11626 				if (s.oninit) {
11627 					l = co = 0;
11628 
11629 					each(el, function(ed) {
11630 						co++;
11631 
11632 						if (!ed.initialized) {
11633 							// Wait for it
11634 							ed.onInit.add(function() {
11635 								l++;
11636 
11637 								// All done
11638 								if (l == co)
11639 									execCallback(s, 'oninit');
11640 							});
11641 						} else
11642 							l++;
11643 
11644 						// All done
11645 						if (l == co)
11646 							execCallback(s, 'oninit');					
11647 					});
11648 				}
11649 			});
11650 		},
11651 
11652 		get : function(id) {
11653 			if (id === undef)
11654 				return this.editors;
11655 
11656 			return this.editors[id];
11657 		},
11658 
11659 		getInstanceById : function(id) {
11660 			return this.get(id);
11661 		},
11662 
11663 		add : function(editor) {
11664 			var self = this, editors = self.editors;
11665 
11666 			// Add named and index editor instance
11667 			editors[editor.id] = editor;
11668 			editors.push(editor);
11669 
11670 			self._setActive(editor);
11671 			self.onAddEditor.dispatch(self, editor);
11672 
11673 
11674 			// Patch the tinymce.Editor instance with jQuery adapter logic
11675 			if (tinymce.adapter)
11676 				tinymce.adapter.patchEditor(editor);
11677 
11678 
11679 			return editor;
11680 		},
11681 
11682 		remove : function(editor) {
11683 			var t = this, i, editors = t.editors;
11684 
11685 			// Not in the collection
11686 			if (!editors[editor.id])
11687 				return null;
11688 
11689 			delete editors[editor.id];
11690 
11691 			for (i = 0; i < editors.length; i++) {
11692 				if (editors[i] == editor) {
11693 					editors.splice(i, 1);
11694 					break;
11695 				}
11696 			}
11697 
11698 			// Select another editor since the active one was removed
11699 			if (t.activeEditor == editor)
11700 				t._setActive(editors[0]);
11701 
11702 			editor.destroy();
11703 			t.onRemoveEditor.dispatch(t, editor);
11704 
11705 			return editor;
11706 		},
11707 
11708 		execCommand : function(c, u, v) {
11709 			var t = this, ed = t.get(v), w;
11710 
11711 			function clr() {
11712 				ed.destroy();
11713 				w.detachEvent('onunload', clr);
11714 				w = w.tinyMCE = w.tinymce = null; // IE leak
11715 			};
11716 
11717 			// Manager commands
11718 			switch (c) {
11719 				case "mceFocus":
11720 					ed.focus();
11721 					return true;
11722 
11723 				case "mceAddEditor":
11724 				case "mceAddControl":
11725 					if (!t.get(v))
11726 						new tinymce.Editor(v, t.settings).render();
11727 
11728 					return true;
11729 
11730 				case "mceAddFrameControl":
11731 					w = v.window;
11732 
11733 					// Add tinyMCE global instance and tinymce namespace to specified window
11734 					w.tinyMCE = tinyMCE;
11735 					w.tinymce = tinymce;
11736 
11737 					tinymce.DOM.doc = w.document;
11738 					tinymce.DOM.win = w;
11739 
11740 					ed = new tinymce.Editor(v.element_id, v);
11741 					ed.render();
11742 
11743 					// Fix IE memory leaks
11744 					if (tinymce.isIE) {
11745 						w.attachEvent('onunload', clr);
11746 					}
11747 
11748 					v.page_window = null;
11749 
11750 					return true;
11751 
11752 				case "mceRemoveEditor":
11753 				case "mceRemoveControl":
11754 					if (ed)
11755 						ed.remove();
11756 
11757 					return true;
11758 
11759 				case 'mceToggleEditor':
11760 					if (!ed) {
11761 						t.execCommand('mceAddControl', 0, v);
11762 						return true;
11763 					}
11764 
11765 					if (ed.isHidden())
11766 						ed.show();
11767 					else
11768 						ed.hide();
11769 
11770 					return true;
11771 			}
11772 
11773 			// Run command on active editor
11774 			if (t.activeEditor)
11775 				return t.activeEditor.execCommand(c, u, v);
11776 
11777 			return false;
11778 		},
11779 
11780 		execInstanceCommand : function(id, c, u, v) {
11781 			var ed = this.get(id);
11782 
11783 			if (ed)
11784 				return ed.execCommand(c, u, v);
11785 
11786 			return false;
11787 		},
11788 
11789 		triggerSave : function() {
11790 			each(this.editors, function(e) {
11791 				e.save();
11792 			});
11793 		},
11794 
11795 		addI18n : function(p, o) {
11796 			var lo, i18n = this.i18n;
11797 
11798 			if (!tinymce.is(p, 'string')) {
11799 				each(p, function(o, lc) {
11800 					each(o, function(o, g) {
11801 						each(o, function(o, k) {
11802 							if (g === 'common')
11803 								i18n[lc + '.' + k] = o;
11804 							else
11805 								i18n[lc + '.' + g + '.' + k] = o;
11806 						});
11807 					});
11808 				});
11809 			} else {
11810 				each(o, function(o, k) {
11811 					i18n[p + '.' + k] = o;
11812 				});
11813 			}
11814 		},
11815 
11816 		// Private methods
11817 
11818 		_setActive : function(editor) {
11819 			this.selectedInstance = this.activeEditor = editor;
11820 		}
11821 	});
11822 })(tinymce);
11823 
11824 (function(tinymce) {
11825 	// Shorten these names
11826 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, extend = tinymce.extend,
11827 		each = tinymce.each, isGecko = tinymce.isGecko,
11828 		isIE = tinymce.isIE, isWebKit = tinymce.isWebKit, is = tinymce.is,
11829 		ThemeManager = tinymce.ThemeManager, PluginManager = tinymce.PluginManager,
11830 		explode = tinymce.explode;
11831 
11832 	tinymce.create('tinymce.Editor', {
11833 		Editor : function(id, settings) {
11834 			var self = this, TRUE = true;
11835 
11836 			self.settings = settings = extend({
11837 				id : id,
11838 				language : 'en',
11839 				theme : 'advanced',
11840 				skin : 'default',
11841 				delta_width : 0,
11842 				delta_height : 0,
11843 				popup_css : '',
11844 				plugins : '',
11845 				document_base_url : tinymce.documentBaseURL,
11846 				add_form_submit_trigger : TRUE,
11847 				submit_patch : TRUE,
11848 				add_unload_trigger : TRUE,
11849 				convert_urls : TRUE,
11850 				relative_urls : TRUE,
11851 				remove_script_host : TRUE,
11852 				table_inline_editing : false,
11853 				object_resizing : TRUE,
11854 				accessibility_focus : TRUE,
11855 				doctype : tinymce.isIE6 ? '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">' : '<!DOCTYPE>', // Use old doctype on IE 6 to avoid horizontal scroll
11856 				visual : TRUE,
11857 				font_size_style_values : 'xx-small,x-small,small,medium,large,x-large,xx-large',
11858 				font_size_legacy_values : 'xx-small,small,medium,large,x-large,xx-large,300%', // See: http://www.w3.org/TR/CSS2/fonts.html#propdef-font-size
11859 				apply_source_formatting : TRUE,
11860 				directionality : 'ltr',
11861 				forced_root_block : 'p',
11862 				hidden_input : TRUE,
11863 				padd_empty_editor : TRUE,
11864 				render_ui : TRUE,
11865 				indentation : '30px',
11866 				fix_table_elements : TRUE,
11867 				inline_styles : TRUE,
11868 				convert_fonts_to_spans : TRUE,
11869 				indent : 'simple',
11870 				indent_before : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
11871 				indent_after : 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,ul,li,area,table,thead,tfoot,tbody,tr,section,article,hgroup,aside,figure,option,optgroup,datalist',
11872 				validate : TRUE,
11873 				entity_encoding : 'named',
11874 				url_converter : self.convertURL,
11875 				url_converter_scope : self,
11876 				ie7_compat : TRUE
11877 			}, settings);
11878 
11879 			self.id = self.editorId = id;
11880 
11881 			self.isNotDirty = false;
11882 
11883 			self.plugins = {};
11884 
11885 			self.documentBaseURI = new tinymce.util.URI(settings.document_base_url || tinymce.documentBaseURL, {
11886 				base_uri : tinyMCE.baseURI
11887 			});
11888 
11889 			self.baseURI = tinymce.baseURI;
11890 
11891 			self.contentCSS = [];
11892 
11893 			self.contentStyles = [];
11894 
11895 			// Creates all events like onClick, onSetContent etc see Editor.Events.js for the actual logic
11896 			self.setupEvents();
11897 
11898 			// Internal command handler objects
11899 			self.execCommands = {};
11900 			self.queryStateCommands = {};
11901 			self.queryValueCommands = {};
11902 
11903 			// Call setup
11904 			self.execCallback('setup', self);
11905 		},
11906 
11907 		render : function(nst) {
11908 			var t = this, s = t.settings, id = t.id, sl = tinymce.ScriptLoader;
11909 
11910 			// Page is not loaded yet, wait for it
11911 			if (!Event.domLoaded) {
11912 				Event.add(window, 'ready', function() {
11913 					t.render();
11914 				});
11915 				return;
11916 			}
11917 
11918 			tinyMCE.settings = s;
11919 
11920 			// Element not found, then skip initialization
11921 			if (!t.getElement())
11922 				return;
11923 
11924 			// Is a iPad/iPhone and not on iOS5, then skip initialization. We need to sniff 
11925 			// here since the browser says it has contentEditable support but there is no visible caret.
11926 			if (tinymce.isIDevice && !tinymce.isIOS5)
11927 				return;
11928 
11929 			// Add hidden input for non input elements inside form elements
11930 			if (!/TEXTAREA|INPUT/i.test(t.getElement().nodeName) && s.hidden_input && DOM.getParent(id, 'form'))
11931 				DOM.insertAfter(DOM.create('input', {type : 'hidden', name : id}), id);
11932 
11933 			// Hide target element early to prevent content flashing
11934 			if (!s.content_editable) {
11935 				t.orgVisibility = t.getElement().style.visibility;
11936 				t.getElement().style.visibility = 'hidden';
11937 			}
11938 
11939 			if (tinymce.WindowManager)
11940 				t.windowManager = new tinymce.WindowManager(t);
11941 
11942 			if (s.encoding == 'xml') {
11943 				t.onGetContent.add(function(ed, o) {
11944 					if (o.save)
11945 						o.content = DOM.encode(o.content);
11946 				});
11947 			}
11948 
11949 			if (s.add_form_submit_trigger) {
11950 				t.onSubmit.addToTop(function() {
11951 					if (t.initialized) {
11952 						t.save();
11953 						t.isNotDirty = 1;
11954 					}
11955 				});
11956 			}
11957 
11958 			if (s.add_unload_trigger) {
11959 				t._beforeUnload = tinyMCE.onBeforeUnload.add(function() {
11960 					if (t.initialized && !t.destroyed && !t.isHidden())
11961 						t.save({format : 'raw', no_events : true});
11962 				});
11963 			}
11964 
11965 			tinymce.addUnload(t.destroy, t);
11966 
11967 			if (s.submit_patch) {
11968 				t.onBeforeRenderUI.add(function() {
11969 					var n = t.getElement().form;
11970 
11971 					if (!n)
11972 						return;
11973 
11974 					// Already patched
11975 					if (n._mceOldSubmit)
11976 						return;
11977 
11978 					// Check page uses id="submit" or name="submit" for it's submit button
11979 					if (!n.submit.nodeType && !n.submit.length) {
11980 						t.formElement = n;
11981 						n._mceOldSubmit = n.submit;
11982 						n.submit = function() {
11983 							// Save all instances
11984 							tinymce.triggerSave();
11985 							t.isNotDirty = 1;
11986 
11987 							return t.formElement._mceOldSubmit(t.formElement);
11988 						};
11989 					}
11990 
11991 					n = null;
11992 				});
11993 			}
11994 
11995 			// Load scripts
11996 			function loadScripts() {
11997 				if (s.language && s.language_load !== false)
11998 					sl.add(tinymce.baseURL + '/langs/' + s.language + '.js');
11999 
12000 				if (s.theme && typeof s.theme != "function" && s.theme.charAt(0) != '-' && !ThemeManager.urls[s.theme])
12001 					ThemeManager.load(s.theme, 'themes/' + s.theme + '/editor_template' + tinymce.suffix + '.js');
12002 
12003 				each(explode(s.plugins), function(p) {
12004 					if (p &&!PluginManager.urls[p]) {
12005 						if (p.charAt(0) == '-') {
12006 							p = p.substr(1, p.length);
12007 							var dependencies = PluginManager.dependencies(p);
12008 							each(dependencies, function(dep) {
12009 								var defaultSettings = {prefix:'plugins/', resource: dep, suffix:'/editor_plugin' + tinymce.suffix + '.js'};
12010 								dep = PluginManager.createUrl(defaultSettings, dep);
12011 								PluginManager.load(dep.resource, dep);
12012 							});
12013 						} else {
12014 							// Skip safari plugin, since it is removed as of 3.3b1
12015 							if (p == 'safari') {
12016 								return;
12017 							}
12018 							PluginManager.load(p, {prefix:'plugins/', resource: p, suffix:'/editor_plugin' + tinymce.suffix + '.js'});
12019 						}
12020 					}
12021 				});
12022 
12023 				// Init when que is loaded
12024 				sl.loadQueue(function() {
12025 					if (!t.removed)
12026 						t.init();
12027 				});
12028 			};
12029 
12030 			loadScripts();
12031 		},
12032 
12033 		init : function() {
12034 			var n, t = this, s = t.settings, w, h, mh, e = t.getElement(), o, ti, u, bi, bc, re, i, initializedPlugins = [];
12035 
12036 			tinymce.add(t);
12037 
12038 			s.aria_label = s.aria_label || DOM.getAttrib(e, 'aria-label', t.getLang('aria.rich_text_area'));
12039 
12040 			if (s.theme) {
12041 				if (typeof s.theme != "function") {
12042 					s.theme = s.theme.replace(/-/, '');
12043 					o = ThemeManager.get(s.theme);
12044 					t.theme = new o();
12045 
12046 					if (t.theme.init)
12047 						t.theme.init(t, ThemeManager.urls[s.theme] || tinymce.documentBaseURL.replace(/\/$/, ''));
12048 				} else {
12049 					t.theme = s.theme;
12050 				}
12051 			}
12052 
12053 			function initPlugin(p) {
12054 				var c = PluginManager.get(p), u = PluginManager.urls[p] || tinymce.documentBaseURL.replace(/\/$/, ''), po;
12055 				if (c && tinymce.inArray(initializedPlugins,p) === -1) {
12056 					each(PluginManager.dependencies(p), function(dep){
12057 						initPlugin(dep);
12058 					});
12059 					po = new c(t, u);
12060 
12061 					t.plugins[p] = po;
12062 
12063 					if (po.init) {
12064 						po.init(t, u);
12065 						initializedPlugins.push(p);
12066 					}
12067 				}
12068 			}
12069 			
12070 			// Create all plugins
12071 			each(explode(s.plugins.replace(/\-/g, '')), initPlugin);
12072 
12073 			// Setup popup CSS path(s)
12074 			if (s.popup_css !== false) {
12075 				if (s.popup_css)
12076 					s.popup_css = t.documentBaseURI.toAbsolute(s.popup_css);
12077 				else
12078 					s.popup_css = t.baseURI.toAbsolute("themes/" + s.theme + "/skins/" + s.skin + "/dialog.css");
12079 			}
12080 
12081 			if (s.popup_css_add)
12082 				s.popup_css += ',' + t.documentBaseURI.toAbsolute(s.popup_css_add);
12083 
12084 			t.controlManager = new tinymce.ControlManager(t);
12085 
12086 			// Enables users to override the control factory
12087 			t.onBeforeRenderUI.dispatch(t, t.controlManager);
12088 
12089 			// Measure box
12090 			if (s.render_ui && t.theme) {
12091 				t.orgDisplay = e.style.display;
12092 
12093 				if (typeof s.theme != "function") {
12094 					w = s.width || e.style.width || e.offsetWidth;
12095 					h = s.height || e.style.height || e.offsetHeight;
12096 					mh = s.min_height || 100;
12097 					re = /^[0-9\.]+(|px)$/i;
12098 
12099 					if (re.test('' + w))
12100 						w = Math.max(parseInt(w, 10) + (o.deltaWidth || 0), 100);
12101 
12102 					if (re.test('' + h))
12103 						h = Math.max(parseInt(h, 10) + (o.deltaHeight || 0), mh);
12104 
12105 					// Render UI
12106 					o = t.theme.renderUI({
12107 						targetNode : e,
12108 						width : w,
12109 						height : h,
12110 						deltaWidth : s.delta_width,
12111 						deltaHeight : s.delta_height
12112 					});
12113 
12114 					// Resize editor
12115 					DOM.setStyles(o.sizeContainer || o.editorContainer, {
12116 						width : w,
12117 						height : h
12118 					});
12119 
12120 					h = (o.iframeHeight || h) + (typeof(h) == 'number' ? (o.deltaHeight || 0) : '');
12121 					if (h < mh)
12122 						h = mh;
12123 				} else {
12124 					o = s.theme(t, e);
12125 
12126 					// Convert element type to id:s
12127 					if (o.editorContainer.nodeType) {
12128 						o.editorContainer = o.editorContainer.id = o.editorContainer.id || t.id + "_parent";
12129 					}
12130 
12131 					// Convert element type to id:s
12132 					if (o.iframeContainer.nodeType) {
12133 						o.iframeContainer = o.iframeContainer.id = o.iframeContainer.id || t.id + "_iframecontainer";
12134 					}
12135 
12136 					// Use specified iframe height or the targets offsetHeight
12137 					h = o.iframeHeight || e.offsetHeight;
12138 
12139 					// Store away the selection when it's changed to it can be restored later with a editor.focus() call
12140 					if (isIE) {
12141 						t.onInit.add(function(ed) {
12142 							ed.dom.bind(ed.getBody(), 'beforedeactivate keydown', function() {
12143 								ed.lastIERng = ed.selection.getRng();
12144 							});
12145 						});
12146 					}
12147 				}
12148 
12149 				t.editorContainer = o.editorContainer;
12150 			}
12151 
12152 			// Load specified content CSS last
12153 			if (s.content_css) {
12154 				each(explode(s.content_css), function(u) {
12155 					t.contentCSS.push(t.documentBaseURI.toAbsolute(u));
12156 				});
12157 			}
12158 
12159 			// Content editable mode ends here
12160 			if (s.content_editable) {
12161 				e = n = o = null; // Fix IE leak
12162 				return t.initContentBody();
12163 			}
12164 
12165 			// User specified a document.domain value
12166 			if (document.domain && location.hostname != document.domain)
12167 				tinymce.relaxedDomain = document.domain;
12168 
12169 			t.iframeHTML = s.doctype + '<html><head xmlns="http://www.w3.org/1999/xhtml">';
12170 
12171 			// We only need to override paths if we have to
12172 			// IE has a bug where it remove site absolute urls to relative ones if this is specified
12173 			if (s.document_base_url != tinymce.documentBaseURL)
12174 				t.iframeHTML += '<base href="' + t.documentBaseURI.getURI() + '" />';
12175 
12176 			// IE8 doesn't support carets behind images setting ie7_compat would force IE8+ to run in IE7 compat mode.
12177 			if (s.ie7_compat)
12178 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=7" />';
12179 			else
12180 				t.iframeHTML += '<meta http-equiv="X-UA-Compatible" content="IE=edge" />';
12181 
12182 			t.iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
12183 
12184 			// Load the CSS by injecting them into the HTML this will reduce "flicker"
12185 			for (i = 0; i < t.contentCSS.length; i++) {
12186 				t.iframeHTML += '<link type="text/css" rel="stylesheet" href="' + t.contentCSS[i] + '" />';
12187 			}
12188 
12189 			t.contentCSS = [];
12190 
12191 			bi = s.body_id || 'tinymce';
12192 			if (bi.indexOf('=') != -1) {
12193 				bi = t.getParam('body_id', '', 'hash');
12194 				bi = bi[t.id] || bi;
12195 			}
12196 
12197 			bc = s.body_class || '';
12198 			if (bc.indexOf('=') != -1) {
12199 				bc = t.getParam('body_class', '', 'hash');
12200 				bc = bc[t.id] || '';
12201 			}
12202 
12203 			t.iframeHTML += '</head><body id="' + bi + '" class="mceContentBody ' + bc + '" onload="window.parent.tinyMCE.get(\'' + t.id + '\').onLoad.dispatch();"><br></body></html>';
12204 
12205 			// Domain relaxing enabled, then set document domain
12206 			if (tinymce.relaxedDomain && (isIE || (tinymce.isOpera && parseFloat(opera.version()) < 11))) {
12207 				// We need to write the contents here in IE since multiple writes messes up refresh button and back button
12208 				u = 'javascript:(function(){document.open();document.domain="' + document.domain + '";var ed = window.parent.tinyMCE.get("' + t.id + '");document.write(ed.iframeHTML);document.close();ed.initContentBody();})()';
12209 			}
12210 
12211 			// Create iframe
12212 			// TODO: ACC add the appropriate description on this.
12213 			n = DOM.add(o.iframeContainer, 'iframe', { 
12214 				id : t.id + "_ifr",
12215 				src : u || 'javascript:""', // Workaround for HTTPS warning in IE6/7
12216 				frameBorder : '0',
12217 				allowTransparency : "true",
12218 				title : s.aria_label,
12219 				style : {
12220 					width : '100%',
12221 					height : h,
12222 					display : 'block' // Important for Gecko to render the iframe correctly
12223 				}
12224 			});
12225 
12226 			t.contentAreaContainer = o.iframeContainer;
12227 
12228 			if (o.editorContainer) {
12229 				DOM.get(o.editorContainer).style.display = t.orgDisplay;
12230 			}
12231 
12232 			// Restore visibility on target element
12233 			e.style.visibility = t.orgVisibility;
12234 
12235 			DOM.get(t.id).style.display = 'none';
12236 			DOM.setAttrib(t.id, 'aria-hidden', true);
12237 
12238 			if (!tinymce.relaxedDomain || !u)
12239 				t.initContentBody();
12240 
12241 			e = n = o = null; // Cleanup
12242 		},
12243 
12244 		initContentBody : function() {
12245 			var self = this, settings = self.settings, targetElm = DOM.get(self.id), doc = self.getDoc(), html, body, contentCssText;
12246 
12247 			// Setup iframe body
12248 			if ((!isIE || !tinymce.relaxedDomain) && !settings.content_editable) {
12249 				doc.open();
12250 				doc.write(self.iframeHTML);
12251 				doc.close();
12252 
12253 				if (tinymce.relaxedDomain)
12254 					doc.domain = tinymce.relaxedDomain;
12255 			}
12256 
12257 			if (settings.content_editable) {
12258 				DOM.addClass(targetElm, 'mceContentBody');
12259 				self.contentDocument = doc = settings.content_document || document;
12260 				self.contentWindow = settings.content_window || window;
12261 				self.bodyElement = targetElm;
12262 
12263 				// Prevent leak in IE
12264 				settings.content_document = settings.content_window = null;
12265 			}
12266 
12267 			// It will not steal focus while setting contentEditable
12268 			body = self.getBody();
12269 			body.disabled = true;
12270 
12271 			if (!settings.readonly)
12272 				body.contentEditable = self.getParam('content_editable_state', true);
12273 
12274 			body.disabled = false;
12275 
12276 			self.schema = new tinymce.html.Schema(settings);
12277 
12278 			self.dom = new tinymce.dom.DOMUtils(doc, {
12279 				keep_values : true,
12280 				url_converter : self.convertURL,
12281 				url_converter_scope : self,
12282 				hex_colors : settings.force_hex_style_colors,
12283 				class_filter : settings.class_filter,
12284 				update_styles : true,
12285 				root_element : settings.content_editable ? self.id : null,
12286 				schema : self.schema
12287 			});
12288 
12289 			self.parser = new tinymce.html.DomParser(settings, self.schema);
12290 
12291 			// Convert src and href into data-mce-src, data-mce-href and data-mce-style
12292 			self.parser.addAttributeFilter('src,href,style', function(nodes, name) {
12293 				var i = nodes.length, node, dom = self.dom, value, internalName;
12294 
12295 				while (i--) {
12296 					node = nodes[i];
12297 					value = node.attr(name);
12298 					internalName = 'data-mce-' + name;
12299 
12300 					// Add internal attribute if we need to we don't on a refresh of the document
12301 					if (!node.attributes.map[internalName]) {	
12302 						if (name === "style")
12303 							node.attr(internalName, dom.serializeStyle(dom.parseStyle(value), node.name));
12304 						else
12305 							node.attr(internalName, self.convertURL(value, name, node.name));
12306 					}
12307 				}
12308 			});
12309 
12310 			// Keep scripts from executing
12311 			self.parser.addNodeFilter('script', function(nodes, name) {
12312 				var i = nodes.length, node;
12313 
12314 				while (i--) {
12315 					node = nodes[i];
12316 					node.attr('type', 'mce-' + (node.attr('type') || 'text/javascript'));
12317 				}
12318 			});
12319 
12320 			self.parser.addNodeFilter('#cdata', function(nodes, name) {
12321 				var i = nodes.length, node;
12322 
12323 				while (i--) {
12324 					node = nodes[i];
12325 					node.type = 8;
12326 					node.name = '#comment';
12327 					node.value = '[CDATA[' + node.value + ']]';
12328 				}
12329 			});
12330 
12331 			self.parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', function(nodes, name) {
12332 				var i = nodes.length, node, nonEmptyElements = self.schema.getNonEmptyElements();
12333 
12334 				while (i--) {
12335 					node = nodes[i];
12336 
12337 					if (node.isEmpty(nonEmptyElements))
12338 						node.empty().append(new tinymce.html.Node('br', 1)).shortEnded = true;
12339 				}
12340 			});
12341 
12342 			self.serializer = new tinymce.dom.Serializer(settings, self.dom, self.schema);
12343 
12344 			self.selection = new tinymce.dom.Selection(self.dom, self.getWin(), self.serializer, self);
12345 
12346 			self.formatter = new tinymce.Formatter(self);
12347 
12348 			self.undoManager = new tinymce.UndoManager(self);
12349 
12350 			self.forceBlocks = new tinymce.ForceBlocks(self);
12351 			self.enterKey = new tinymce.EnterKey(self);
12352 			self.editorCommands = new tinymce.EditorCommands(self);
12353 
12354 			self.onExecCommand.add(function(editor, command) {
12355 				// Don't refresh the select lists until caret move
12356 				if (!/^(FontName|FontSize)$/.test(command))
12357 					self.nodeChanged();
12358 			});
12359 
12360 			// Pass through
12361 			self.serializer.onPreProcess.add(function(se, o) {
12362 				return self.onPreProcess.dispatch(self, o, se);
12363 			});
12364 
12365 			self.serializer.onPostProcess.add(function(se, o) {
12366 				return self.onPostProcess.dispatch(self, o, se);
12367 			});
12368 
12369 			self.onPreInit.dispatch(self);
12370 
12371 			if (!settings.browser_spellcheck && !settings.gecko_spellcheck)
12372 				doc.body.spellcheck = false;
12373 
12374 			if (!settings.readonly) {
12375 				self.bindNativeEvents();
12376 			}
12377 
12378 			self.controlManager.onPostRender.dispatch(self, self.controlManager);
12379 			self.onPostRender.dispatch(self);
12380 
12381 			self.quirks = tinymce.util.Quirks(self);
12382 
12383 			if (settings.directionality)
12384 				body.dir = settings.directionality;
12385 
12386 			if (settings.nowrap)
12387 				body.style.whiteSpace = "nowrap";
12388 
12389 			if (settings.protect) {
12390 				self.onBeforeSetContent.add(function(ed, o) {
12391 					each(settings.protect, function(pattern) {
12392 						o.content = o.content.replace(pattern, function(str) {
12393 							return '<!--mce:protected ' + escape(str) + '-->';
12394 						});
12395 					});
12396 				});
12397 			}
12398 
12399 			// Add visual aids when new contents is added
12400 			self.onSetContent.add(function() {
12401 				self.addVisual(self.getBody());
12402 			});
12403 
12404 			// Remove empty contents
12405 			if (settings.padd_empty_editor) {
12406 				self.onPostProcess.add(function(ed, o) {
12407 					o.content = o.content.replace(/^(<p[^>]*>( | |\s|\u00a0|)<\/p>[\r\n]*|<br \/>[\r\n]*)$/, '');
12408 				});
12409 			}
12410 
12411 			self.load({initial : true, format : 'html'});
12412 			self.startContent = self.getContent({format : 'raw'});
12413 
12414 			self.initialized = true;
12415 
12416 			self.onInit.dispatch(self);
12417 			self.execCallback('setupcontent_callback', self.id, body, doc);
12418 			self.execCallback('init_instance_callback', self);
12419 			self.focus(true);
12420 			self.nodeChanged({initial : true});
12421 
12422 			// Add editor specific CSS styles
12423 			if (self.contentStyles.length > 0) {
12424 				contentCssText = '';
12425 
12426 				each(self.contentStyles, function(style) {
12427 					contentCssText += style + "\r\n";
12428 				});
12429 
12430 				self.dom.addStyle(contentCssText);
12431 			}
12432 
12433 			// Load specified content CSS last
12434 			each(self.contentCSS, function(url) {
12435 				self.dom.loadCSS(url);
12436 			});
12437 
12438 			// Handle auto focus
12439 			if (settings.auto_focus) {
12440 				setTimeout(function () {
12441 					var ed = tinymce.get(settings.auto_focus);
12442 
12443 					ed.selection.select(ed.getBody(), 1);
12444 					ed.selection.collapse(1);
12445 					ed.getBody().focus();
12446 					ed.getWin().focus();
12447 				}, 100);
12448 			}
12449 
12450 			// Clean up references for IE
12451 			targetElm = doc = body = null;
12452 		},
12453 
12454 		focus : function(skip_focus) {
12455 			var oed, self = this, selection = self.selection, contentEditable = self.settings.content_editable, ieRng, controlElm, doc = self.getDoc(), body;
12456 
12457 			if (!skip_focus) {
12458 				if (self.lastIERng) {
12459 					selection.setRng(self.lastIERng);
12460 				}
12461 
12462 				// Get selected control element
12463 				ieRng = selection.getRng();
12464 				if (ieRng.item) {
12465 					controlElm = ieRng.item(0);
12466 				}
12467 
12468 				self._refreshContentEditable();
12469 
12470 				// Focus the window iframe
12471 				if (!contentEditable) {
12472 					self.getWin().focus();
12473 				}
12474 
12475 				// Focus the body as well since it's contentEditable
12476 				if (tinymce.isGecko || contentEditable) {
12477 					body = self.getBody();
12478 
12479 					// Check for setActive since it doesn't scroll to the element
12480 					if (body.setActive) {
12481 						body.setActive();
12482 					} else {
12483 						body.focus();
12484 					}
12485 
12486 					if (contentEditable) {
12487 						selection.normalize();
12488 					}
12489 				}
12490 
12491 				// Restore selected control element
12492 				// This is needed when for example an image is selected within a
12493 				// layer a call to focus will then remove the control selection
12494 				if (controlElm && controlElm.ownerDocument == doc) {
12495 					ieRng = doc.body.createControlRange();
12496 					ieRng.addElement(controlElm);
12497 					ieRng.select();
12498 				}
12499 			}
12500 
12501 			if (tinymce.activeEditor != self) {
12502 				if ((oed = tinymce.activeEditor) != null)
12503 					oed.onDeactivate.dispatch(oed, self);
12504 
12505 				self.onActivate.dispatch(self, oed);
12506 			}
12507 
12508 			tinymce._setActive(self);
12509 		},
12510 
12511 		execCallback : function(n) {
12512 			var t = this, f = t.settings[n], s;
12513 
12514 			if (!f)
12515 				return;
12516 
12517 			// Look through lookup
12518 			if (t.callbackLookup && (s = t.callbackLookup[n])) {
12519 				f = s.func;
12520 				s = s.scope;
12521 			}
12522 
12523 			if (is(f, 'string')) {
12524 				s = f.replace(/\.\w+$/, '');
12525 				s = s ? tinymce.resolve(s) : 0;
12526 				f = tinymce.resolve(f);
12527 				t.callbackLookup = t.callbackLookup || {};
12528 				t.callbackLookup[n] = {func : f, scope : s};
12529 			}
12530 
12531 			return f.apply(s || t, Array.prototype.slice.call(arguments, 1));
12532 		},
12533 
12534 		translate : function(s) {
12535 			var c = this.settings.language || 'en', i18n = tinymce.i18n;
12536 
12537 			if (!s)
12538 				return '';
12539 
12540 			return i18n[c + '.' + s] || s.replace(/\{\#([^\}]+)\}/g, function(a, b) {
12541 				return i18n[c + '.' + b] || '{#' + b + '}';
12542 			});
12543 		},
12544 
12545 		getLang : function(n, dv) {
12546 			return tinymce.i18n[(this.settings.language || 'en') + '.' + n] || (is(dv) ? dv : '{#' + n + '}');
12547 		},
12548 
12549 		getParam : function(n, dv, ty) {
12550 			var tr = tinymce.trim, v = is(this.settings[n]) ? this.settings[n] : dv, o;
12551 
12552 			if (ty === 'hash') {
12553 				o = {};
12554 
12555 				if (is(v, 'string')) {
12556 					each(v.indexOf('=') > 0 ? v.split(/[;,](?![^=;,]*(?:[;,]|$))/) : v.split(','), function(v) {
12557 						v = v.split('=');
12558 
12559 						if (v.length > 1)
12560 							o[tr(v[0])] = tr(v[1]);
12561 						else
12562 							o[tr(v[0])] = tr(v);
12563 					});
12564 				} else
12565 					o = v;
12566 
12567 				return o;
12568 			}
12569 
12570 			return v;
12571 		},
12572 
12573 		nodeChanged : function(o) {
12574 			var self = this, selection = self.selection, node;
12575 
12576 			// Fix for bug #1896577 it seems that this can not be fired while the editor is loading
12577 			if (self.initialized) {
12578 				o = o || {};
12579 
12580 				// Get start node
12581 				node = selection.getStart() || self.getBody();
12582 				node = isIE && node.ownerDocument != self.getDoc() ? self.getBody() : node; // Fix for IE initial state
12583 
12584 				// Get parents and add them to object
12585 				o.parents = [];
12586 				self.dom.getParent(node, function(node) {
12587 					if (node.nodeName == 'BODY')
12588 						return true;
12589 
12590 					o.parents.push(node);
12591 				});
12592 
12593 				self.onNodeChange.dispatch(
12594 					self,
12595 					o ? o.controlManager || self.controlManager : self.controlManager,
12596 					node,
12597 					selection.isCollapsed(),
12598 					o
12599 				);
12600 			}
12601 		},
12602 
12603 		addButton : function(name, settings) {
12604 			var self = this;
12605 
12606 			self.buttons = self.buttons || {};
12607 			self.buttons[name] = settings;
12608 		},
12609 
12610 		addCommand : function(name, callback, scope) {
12611 			this.execCommands[name] = {func : callback, scope : scope || this};
12612 		},
12613 
12614 		addQueryStateHandler : function(name, callback, scope) {
12615 			this.queryStateCommands[name] = {func : callback, scope : scope || this};
12616 		},
12617 
12618 		addQueryValueHandler : function(name, callback, scope) {
12619 			this.queryValueCommands[name] = {func : callback, scope : scope || this};
12620 		},
12621 
12622 		addShortcut : function(pa, desc, cmd_func, sc) {
12623 			var t = this, c;
12624 
12625 			if (t.settings.custom_shortcuts === false)
12626 				return false;
12627 
12628 			t.shortcuts = t.shortcuts || {};
12629 
12630 			if (is(cmd_func, 'string')) {
12631 				c = cmd_func;
12632 
12633 				cmd_func = function() {
12634 					t.execCommand(c, false, null);
12635 				};
12636 			}
12637 
12638 			if (is(cmd_func, 'object')) {
12639 				c = cmd_func;
12640 
12641 				cmd_func = function() {
12642 					t.execCommand(c[0], c[1], c[2]);
12643 				};
12644 			}
12645 
12646 			each(explode(pa), function(pa) {
12647 				var o = {
12648 					func : cmd_func,
12649 					scope : sc || this,
12650 					desc : t.translate(desc),
12651 					alt : false,
12652 					ctrl : false,
12653 					shift : false
12654 				};
12655 
12656 				each(explode(pa, '+'), function(v) {
12657 					switch (v) {
12658 						case 'alt':
12659 						case 'ctrl':
12660 						case 'shift':
12661 							o[v] = true;
12662 							break;
12663 
12664 						default:
12665 							o.charCode = v.charCodeAt(0);
12666 							o.keyCode = v.toUpperCase().charCodeAt(0);
12667 					}
12668 				});
12669 
12670 				t.shortcuts[(o.ctrl ? 'ctrl' : '') + ',' + (o.alt ? 'alt' : '') + ',' + (o.shift ? 'shift' : '') + ',' + o.keyCode] = o;
12671 			});
12672 
12673 			return true;
12674 		},
12675 
12676 		execCommand : function(cmd, ui, val, a) {
12677 			var t = this, s = 0, o, st;
12678 
12679 			if (!/^(mceAddUndoLevel|mceEndUndoLevel|mceBeginUndoLevel|mceRepaint|SelectAll)$/.test(cmd) && (!a || !a.skip_focus))
12680 				t.focus();
12681 
12682 			a = extend({}, a);
12683 			t.onBeforeExecCommand.dispatch(t, cmd, ui, val, a);
12684 			if (a.terminate)
12685 				return false;
12686 
12687 			// Command callback
12688 			if (t.execCallback('execcommand_callback', t.id, t.selection.getNode(), cmd, ui, val)) {
12689 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12690 				return true;
12691 			}
12692 
12693 			// Registred commands
12694 			if (o = t.execCommands[cmd]) {
12695 				st = o.func.call(o.scope, ui, val);
12696 
12697 				// Fall through on true
12698 				if (st !== true) {
12699 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
12700 					return st;
12701 				}
12702 			}
12703 
12704 			// Plugin commands
12705 			each(t.plugins, function(p) {
12706 				if (p.execCommand && p.execCommand(cmd, ui, val)) {
12707 					t.onExecCommand.dispatch(t, cmd, ui, val, a);
12708 					s = 1;
12709 					return false;
12710 				}
12711 			});
12712 
12713 			if (s)
12714 				return true;
12715 
12716 			// Theme commands
12717 			if (t.theme && t.theme.execCommand && t.theme.execCommand(cmd, ui, val)) {
12718 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12719 				return true;
12720 			}
12721 
12722 			// Editor commands
12723 			if (t.editorCommands.execCommand(cmd, ui, val)) {
12724 				t.onExecCommand.dispatch(t, cmd, ui, val, a);
12725 				return true;
12726 			}
12727 
12728 			// Browser commands
12729 			t.getDoc().execCommand(cmd, ui, val);
12730 			t.onExecCommand.dispatch(t, cmd, ui, val, a);
12731 		},
12732 
12733 		queryCommandState : function(cmd) {
12734 			var t = this, o, s;
12735 
12736 			// Is hidden then return undefined
12737 			if (t._isHidden())
12738 				return;
12739 
12740 			// Registred commands
12741 			if (o = t.queryStateCommands[cmd]) {
12742 				s = o.func.call(o.scope);
12743 
12744 				// Fall though on true
12745 				if (s !== true)
12746 					return s;
12747 			}
12748 
12749 			// Registred commands
12750 			o = t.editorCommands.queryCommandState(cmd);
12751 			if (o !== -1)
12752 				return o;
12753 
12754 			// Browser commands
12755 			try {
12756 				return this.getDoc().queryCommandState(cmd);
12757 			} catch (ex) {
12758 				// Fails sometimes see bug: 1896577
12759 			}
12760 		},
12761 
12762 		queryCommandValue : function(c) {
12763 			var t = this, o, s;
12764 
12765 			// Is hidden then return undefined
12766 			if (t._isHidden())
12767 				return;
12768 
12769 			// Registred commands
12770 			if (o = t.queryValueCommands[c]) {
12771 				s = o.func.call(o.scope);
12772 
12773 				// Fall though on true
12774 				if (s !== true)
12775 					return s;
12776 			}
12777 
12778 			// Registred commands
12779 			o = t.editorCommands.queryCommandValue(c);
12780 			if (is(o))
12781 				return o;
12782 
12783 			// Browser commands
12784 			try {
12785 				return this.getDoc().queryCommandValue(c);
12786 			} catch (ex) {
12787 				// Fails sometimes see bug: 1896577
12788 			}
12789 		},
12790 
12791 		show : function() {
12792 			var self = this;
12793 
12794 			DOM.show(self.getContainer());
12795 			DOM.hide(self.id);
12796 			self.load();
12797 		},
12798 
12799 		hide : function() {
12800 			var self = this, doc = self.getDoc();
12801 
12802 			// Fixed bug where IE has a blinking cursor left from the editor
12803 			if (isIE && doc)
12804 				doc.execCommand('SelectAll');
12805 
12806 			// We must save before we hide so Safari doesn't crash
12807 			self.save();
12808 			DOM.hide(self.getContainer());
12809 			DOM.setStyle(self.id, 'display', self.orgDisplay);
12810 		},
12811 
12812 		isHidden : function() {
12813 			return !DOM.isHidden(this.id);
12814 		},
12815 
12816 		setProgressState : function(b, ti, o) {
12817 			this.onSetProgressState.dispatch(this, b, ti, o);
12818 
12819 			return b;
12820 		},
12821 
12822 		load : function(o) {
12823 			var t = this, e = t.getElement(), h;
12824 
12825 			if (e) {
12826 				o = o || {};
12827 				o.load = true;
12828 
12829 				// Double encode existing entities in the value
12830 				h = t.setContent(is(e.value) ? e.value : e.innerHTML, o);
12831 				o.element = e;
12832 
12833 				if (!o.no_events)
12834 					t.onLoadContent.dispatch(t, o);
12835 
12836 				o.element = e = null;
12837 
12838 				return h;
12839 			}
12840 		},
12841 
12842 		save : function(o) {
12843 			var t = this, e = t.getElement(), h, f;
12844 
12845 			if (!e || !t.initialized)
12846 				return;
12847 
12848 			o = o || {};
12849 			o.save = true;
12850 
12851 			o.element = e;
12852 			h = o.content = t.getContent(o);
12853 
12854 			if (!o.no_events)
12855 				t.onSaveContent.dispatch(t, o);
12856 
12857 			h = o.content;
12858 
12859 			if (!/TEXTAREA|INPUT/i.test(e.nodeName)) {
12860 				e.innerHTML = h;
12861 
12862 				// Update hidden form element
12863 				if (f = DOM.getParent(t.id, 'form')) {
12864 					each(f.elements, function(e) {
12865 						if (e.name == t.id) {
12866 							e.value = h;
12867 							return false;
12868 						}
12869 					});
12870 				}
12871 			} else
12872 				e.value = h;
12873 
12874 			o.element = e = null;
12875 
12876 			return h;
12877 		},
12878 
12879 		setContent : function(content, args) {
12880 			var self = this, rootNode, body = self.getBody(), forcedRootBlockName;
12881 
12882 			// Setup args object
12883 			args = args || {};
12884 			args.format = args.format || 'html';
12885 			args.set = true;
12886 			args.content = content;
12887 
12888 			// Do preprocessing
12889 			if (!args.no_events)
12890 				self.onBeforeSetContent.dispatch(self, args);
12891 
12892 			content = args.content;
12893 
12894 			// Padd empty content in Gecko and Safari. Commands will otherwise fail on the content
12895 			// It will also be impossible to place the caret in the editor unless there is a BR element present
12896 			if (!tinymce.isIE && (content.length === 0 || /^\s+$/.test(content))) {
12897 				forcedRootBlockName = self.settings.forced_root_block;
12898 				if (forcedRootBlockName)
12899 					content = '<' + forcedRootBlockName + '><br data-mce-bogus="1"></' + forcedRootBlockName + '>';
12900 				else
12901 					content = '<br data-mce-bogus="1">';
12902 
12903 				body.innerHTML = content;
12904 				self.selection.select(body, true);
12905 				self.selection.collapse(true);
12906 				return;
12907 			}
12908 
12909 			// Parse and serialize the html
12910 			if (args.format !== 'raw') {
12911 				content = new tinymce.html.Serializer({}, self.schema).serialize(
12912 					self.parser.parse(content)
12913 				);
12914 			}
12915 
12916 			// Set the new cleaned contents to the editor
12917 			args.content = tinymce.trim(content);
12918 			self.dom.setHTML(body, args.content);
12919 
12920 			// Do post processing
12921 			if (!args.no_events)
12922 				self.onSetContent.dispatch(self, args);
12923 
12924 			// Don't normalize selection if the focused element isn't the body in content editable mode since it will steal focus otherwise
12925 			if (!self.settings.content_editable || document.activeElement === self.getBody()) {
12926 				self.selection.normalize();
12927 			}
12928 
12929 			return args.content;
12930 		},
12931 
12932 		getContent : function(args) {
12933 			var self = this, content;
12934 
12935 			// Setup args object
12936 			args = args || {};
12937 			args.format = args.format || 'html';
12938 			args.get = true;
12939 			args.getInner = true;
12940 
12941 			// Do preprocessing
12942 			if (!args.no_events)
12943 				self.onBeforeGetContent.dispatch(self, args);
12944 
12945 			// Get raw contents or by default the cleaned contents
12946 			if (args.format == 'raw')
12947 				content = self.getBody().innerHTML;
12948 			else
12949 				content = self.serializer.serialize(self.getBody(), args);
12950 
12951 			args.content = tinymce.trim(content);
12952 
12953 			// Do post processing
12954 			if (!args.no_events)
12955 				self.onGetContent.dispatch(self, args);
12956 
12957 			return args.content;
12958 		},
12959 
12960 		isDirty : function() {
12961 			var self = this;
12962 
12963 			return tinymce.trim(self.startContent) != tinymce.trim(self.getContent({format : 'raw', no_events : 1})) && !self.isNotDirty;
12964 		},
12965 
12966 		getContainer : function() {
12967 			var self = this;
12968 
12969 			if (!self.container)
12970 				self.container = DOM.get(self.editorContainer || self.id + '_parent');
12971 
12972 			return self.container;
12973 		},
12974 
12975 		getContentAreaContainer : function() {
12976 			return this.contentAreaContainer;
12977 		},
12978 
12979 		getElement : function() {
12980 			return DOM.get(this.settings.content_element || this.id);
12981 		},
12982 
12983 		getWin : function() {
12984 			var self = this, elm;
12985 
12986 			if (!self.contentWindow) {
12987 				elm = DOM.get(self.id + "_ifr");
12988 
12989 				if (elm)
12990 					self.contentWindow = elm.contentWindow;
12991 			}
12992 
12993 			return self.contentWindow;
12994 		},
12995 
12996 		getDoc : function() {
12997 			var self = this, win;
12998 
12999 			if (!self.contentDocument) {
13000 				win = self.getWin();
13001 
13002 				if (win)
13003 					self.contentDocument = win.document;
13004 			}
13005 
13006 			return self.contentDocument;
13007 		},
13008 
13009 		getBody : function() {
13010 			return this.bodyElement || this.getDoc().body;
13011 		},
13012 
13013 		convertURL : function(url, name, elm) {
13014 			var self = this, settings = self.settings;
13015 
13016 			// Use callback instead
13017 			if (settings.urlconverter_callback)
13018 				return self.execCallback('urlconverter_callback', url, elm, true, name);
13019 
13020 			// Don't convert link href since thats the CSS files that gets loaded into the editor also skip local file URLs
13021 			if (!settings.convert_urls || (elm && elm.nodeName == 'LINK') || url.indexOf('file:') === 0)
13022 				return url;
13023 
13024 			// Convert to relative
13025 			if (settings.relative_urls)
13026 				return self.documentBaseURI.toRelative(url);
13027 
13028 			// Convert to absolute
13029 			url = self.documentBaseURI.toAbsolute(url, settings.remove_script_host);
13030 
13031 			return url;
13032 		},
13033 
13034 		addVisual : function(elm) {
13035 			var self = this, settings = self.settings, dom = self.dom, cls;
13036 
13037 			elm = elm || self.getBody();
13038 
13039 			if (!is(self.hasVisual))
13040 				self.hasVisual = settings.visual;
13041 
13042 			each(dom.select('table,a', elm), function(elm) {
13043 				var value;
13044 
13045 				switch (elm.nodeName) {
13046 					case 'TABLE':
13047 						cls = settings.visual_table_class || 'mceItemTable';
13048 						value = dom.getAttrib(elm, 'border');
13049 
13050 						if (!value || value == '0') {
13051 							if (self.hasVisual)
13052 								dom.addClass(elm, cls);
13053 							else
13054 								dom.removeClass(elm, cls);
13055 						}
13056 
13057 						return;
13058 
13059 					case 'A':
13060 						if (!dom.getAttrib(elm, 'href', false)) {
13061 							value = dom.getAttrib(elm, 'name') || elm.id;
13062 							cls = 'mceItemAnchor';
13063 
13064 							if (value) {
13065 								if (self.hasVisual)
13066 									dom.addClass(elm, cls);
13067 								else
13068 									dom.removeClass(elm, cls);
13069 							}
13070 						}
13071 
13072 						return;
13073 				}
13074 			});
13075 
13076 			self.onVisualAid.dispatch(self, elm, self.hasVisual);
13077 		},
13078 
13079 		remove : function() {
13080 			var self = this, elm = self.getContainer();
13081 
13082 			if (!self.removed) {
13083 				self.removed = 1; // Cancels post remove event execution
13084 				self.hide();
13085 
13086 				// Don't clear the window or document if content editable
13087 				// is enabled since other instances might still be present
13088 				if (!self.settings.content_editable) {
13089 					Event.unbind(self.getWin());
13090 					Event.unbind(self.getDoc());
13091 				}
13092 
13093 				Event.unbind(self.getBody());
13094 				Event.clear(elm);
13095 
13096 				self.execCallback('remove_instance_callback', self);
13097 				self.onRemove.dispatch(self);
13098 
13099 				// Clear all execCommand listeners this is required to avoid errors if the editor was removed inside another command
13100 				self.onExecCommand.listeners = [];
13101 
13102 				tinymce.remove(self);
13103 				DOM.remove(elm);
13104 			}
13105 		},
13106 
13107 		destroy : function(s) {
13108 			var t = this;
13109 
13110 			// One time is enough
13111 			if (t.destroyed)
13112 				return;
13113 
13114 			// We must unbind on Gecko since it would otherwise produce the pesky "attempt to run compile-and-go script on a cleared scope" message
13115 			if (isGecko) {
13116 				Event.unbind(t.getDoc());
13117 				Event.unbind(t.getWin());
13118 				Event.unbind(t.getBody());
13119 			}
13120 
13121 			if (!s) {
13122 				tinymce.removeUnload(t.destroy);
13123 				tinyMCE.onBeforeUnload.remove(t._beforeUnload);
13124 
13125 				// Manual destroy
13126 				if (t.theme && t.theme.destroy)
13127 					t.theme.destroy();
13128 
13129 				// Destroy controls, selection and dom
13130 				t.controlManager.destroy();
13131 				t.selection.destroy();
13132 				t.dom.destroy();
13133 			}
13134 
13135 			if (t.formElement) {
13136 				t.formElement.submit = t.formElement._mceOldSubmit;
13137 				t.formElement._mceOldSubmit = null;
13138 			}
13139 
13140 			t.contentAreaContainer = t.formElement = t.container = t.settings.content_element = t.bodyElement = t.contentDocument = t.contentWindow = null;
13141 
13142 			if (t.selection)
13143 				t.selection = t.selection.win = t.selection.dom = t.selection.dom.doc = null;
13144 
13145 			t.destroyed = 1;
13146 		},
13147 
13148 		// Internal functions
13149 
13150 		_refreshContentEditable : function() {
13151 			var self = this, body, parent;
13152 
13153 			// Check if the editor was hidden and the re-initalize contentEditable mode by removing and adding the body again
13154 			if (self._isHidden()) {
13155 				body = self.getBody();
13156 				parent = body.parentNode;
13157 
13158 				parent.removeChild(body);
13159 				parent.appendChild(body);
13160 
13161 				body.focus();
13162 			}
13163 		},
13164 
13165 		_isHidden : function() {
13166 			var s;
13167 
13168 			if (!isGecko)
13169 				return 0;
13170 
13171 			// Weird, wheres that cursor selection?
13172 			s = this.selection.getSel();
13173 			return (!s || !s.rangeCount || s.rangeCount === 0);
13174 		}
13175 	});
13176 })(tinymce);
13177 (function(tinymce) {
13178 	var each = tinymce.each;
13179 
13180 	tinymce.Editor.prototype.setupEvents = function() {
13181 		var self = this, settings = self.settings;
13182 
13183 		// Add events to the editor
13184 		each([
13185 			'onPreInit',
13186 
13187 			'onBeforeRenderUI',
13188 
13189 			'onPostRender',
13190 
13191 			'onLoad',
13192 
13193 			'onInit',
13194 
13195 			'onRemove',
13196 
13197 			'onActivate',
13198 
13199 			'onDeactivate',
13200 
13201 			'onClick',
13202 
13203 			'onEvent',
13204 
13205 			'onMouseUp',
13206 
13207 			'onMouseDown',
13208 
13209 			'onDblClick',
13210 
13211 			'onKeyDown',
13212 
13213 			'onKeyUp',
13214 
13215 			'onKeyPress',
13216 
13217 			'onContextMenu',
13218 
13219 			'onSubmit',
13220 
13221 			'onReset',
13222 
13223 			'onPaste',
13224 
13225 			'onPreProcess',
13226 
13227 			'onPostProcess',
13228 
13229 			'onBeforeSetContent',
13230 
13231 			'onBeforeGetContent',
13232 
13233 			'onSetContent',
13234 
13235 			'onGetContent',
13236 
13237 			'onLoadContent',
13238 
13239 			'onSaveContent',
13240 
13241 			'onNodeChange',
13242 
13243 			'onChange',
13244 
13245 			'onBeforeExecCommand',
13246 
13247 			'onExecCommand',
13248 
13249 			'onUndo',
13250 
13251 			'onRedo',
13252 
13253 			'onVisualAid',
13254 
13255 			'onSetProgressState',
13256 
13257 			'onSetAttrib'
13258 		], function(name) {
13259 			self[name] = new tinymce.util.Dispatcher(self);
13260 		});
13261 
13262 		// Handle legacy cleanup_callback option
13263 		if (settings.cleanup_callback) {
13264 			self.onBeforeSetContent.add(function(ed, o) {
13265 				o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
13266 			});
13267 
13268 			self.onPreProcess.add(function(ed, o) {
13269 				if (o.set)
13270 					ed.execCallback('cleanup_callback', 'insert_to_editor_dom', o.node, o);
13271 
13272 				if (o.get)
13273 					ed.execCallback('cleanup_callback', 'get_from_editor_dom', o.node, o);
13274 			});
13275 
13276 			self.onPostProcess.add(function(ed, o) {
13277 				if (o.set)
13278 					o.content = ed.execCallback('cleanup_callback', 'insert_to_editor', o.content, o);
13279 
13280 				if (o.get)						
13281 					o.content = ed.execCallback('cleanup_callback', 'get_from_editor', o.content, o);
13282 			});
13283 		}
13284 
13285 		// Handle legacy save_callback option
13286 		if (settings.save_callback) {
13287 			self.onGetContent.add(function(ed, o) {
13288 				if (o.save)
13289 					o.content = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
13290 			});
13291 		}
13292 
13293 		// Handle legacy handle_event_callback option
13294 		if (settings.handle_event_callback) {
13295 			self.onEvent.add(function(ed, e, o) {
13296 				if (self.execCallback('handle_event_callback', e, ed, o) === false) {
13297 					e.preventDefault();
13298 					e.stopPropagation();
13299 				}
13300 			});
13301 		}
13302 
13303 		// Handle legacy handle_node_change_callback option
13304 		if (settings.handle_node_change_callback) {
13305 			self.onNodeChange.add(function(ed, cm, n) {
13306 				ed.execCallback('handle_node_change_callback', ed.id, n, -1, -1, true, ed.selection.isCollapsed());
13307 			});
13308 		}
13309 
13310 		// Handle legacy save_callback option
13311 		if (settings.save_callback) {
13312 			self.onSaveContent.add(function(ed, o) {
13313 				var h = ed.execCallback('save_callback', ed.id, o.content, ed.getBody());
13314 
13315 				if (h)
13316 					o.content = h;
13317 			});
13318 		}
13319 
13320 		// Handle legacy onchange_callback option
13321 		if (settings.onchange_callback) {
13322 			self.onChange.add(function(ed, l) {
13323 				ed.execCallback('onchange_callback', ed, l);
13324 			});
13325 		}
13326 	};
13327 
13328 	tinymce.Editor.prototype.bindNativeEvents = function() {
13329 		// 'focus', 'blur', 'dblclick', 'beforedeactivate', submit, reset
13330 		var self = this, i, settings = self.settings, dom = self.dom, nativeToDispatcherMap;
13331 
13332 		nativeToDispatcherMap = {
13333 			mouseup : 'onMouseUp',
13334 			mousedown : 'onMouseDown',
13335 			click : 'onClick',
13336 			keyup : 'onKeyUp',
13337 			keydown : 'onKeyDown',
13338 			keypress : 'onKeyPress',
13339 			submit : 'onSubmit',
13340 			reset : 'onReset',
13341 			contextmenu : 'onContextMenu',
13342 			dblclick : 'onDblClick',
13343 			paste : 'onPaste' // Doesn't work in all browsers yet
13344 		};
13345 
13346 		// Handler that takes a native event and sends it out to a dispatcher like onKeyDown
13347 		function eventHandler(evt, args) {
13348 			var type = evt.type;
13349 
13350 			// Don't fire events when it's removed
13351 			if (self.removed)
13352 				return;
13353 
13354 			// Sends the native event out to a global dispatcher then to the specific event dispatcher
13355 			if (self.onEvent.dispatch(self, evt, args) !== false) {
13356 				self[nativeToDispatcherMap[evt.fakeType || evt.type]].dispatch(self, evt, args);
13357 			}
13358 		};
13359 
13360 		// Opera doesn't support focus event for contentEditable elements so we need to fake it
13361 		function doOperaFocus(e) {
13362 			self.focus(true);
13363 		};
13364 
13365 		function nodeChanged(ed, e) {
13366 			// Normalize selection for example <b>a</b><i>|a</i> becomes <b>a|</b><i>a</i> except for Ctrl+A since it selects everything
13367 			if (e.keyCode != 65 || !tinymce.VK.metaKeyPressed(e)) {
13368 				self.selection.normalize();
13369 			}
13370 
13371 			self.nodeChanged();
13372 		}
13373 
13374 		// Add DOM events
13375 		each(nativeToDispatcherMap, function(dispatcherName, nativeName) {
13376 			var root = settings.content_editable ? self.getBody() : self.getDoc();
13377 
13378 			switch (nativeName) {
13379 				case 'contextmenu':
13380 					dom.bind(root, nativeName, eventHandler);
13381 					break;
13382 
13383 				case 'paste':
13384 					dom.bind(self.getBody(), nativeName, eventHandler);
13385 					break;
13386 
13387 				case 'submit':
13388 				case 'reset':
13389 					dom.bind(self.getElement().form || tinymce.DOM.getParent(self.id, 'form'), nativeName, eventHandler);
13390 					break;
13391 
13392 				default:
13393 					dom.bind(root, nativeName, eventHandler);
13394 			}
13395 		});
13396 
13397 		// Set the editor as active when focused
13398 		dom.bind(settings.content_editable ? self.getBody() : (tinymce.isGecko ? self.getDoc() : self.getWin()), 'focus', function(e) {
13399 			self.focus(true);
13400 		});
13401 
13402 		if (settings.content_editable && tinymce.isOpera) {
13403 			dom.bind(self.getBody(), 'click', doOperaFocus);
13404 			dom.bind(self.getBody(), 'keydown', doOperaFocus);
13405 		}
13406 
13407 		// Add node change handler
13408 		self.onMouseUp.add(nodeChanged);
13409 
13410 		self.onKeyUp.add(function(ed, e) {
13411 			var keyCode = e.keyCode;
13412 
13413 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 13 || keyCode == 45 || keyCode == 46 || keyCode == 8 || (tinymce.isMac && (keyCode == 91 || keyCode == 93)) || e.ctrlKey)
13414 				nodeChanged(ed, e);
13415 		});
13416 
13417 		// Add reset handler
13418 		self.onReset.add(function() {
13419 			self.setContent(self.startContent, {format : 'raw'});
13420 		});
13421 
13422 		// Add shortcuts
13423 		function handleShortcut(e, execute) {
13424 			if (e.altKey || e.ctrlKey || e.metaKey) {
13425 				each(self.shortcuts, function(shortcut) {
13426 					var ctrlState = tinymce.isMac ? e.metaKey : e.ctrlKey;
13427 
13428 					if (shortcut.ctrl != ctrlState || shortcut.alt != e.altKey || shortcut.shift != e.shiftKey)
13429 						return;
13430 
13431 					if (e.keyCode == shortcut.keyCode || (e.charCode && e.charCode == shortcut.charCode)) {
13432 						e.preventDefault();
13433 
13434 						if (execute) {
13435 							shortcut.func.call(shortcut.scope);
13436 						}
13437 
13438 						return true;
13439 					}
13440 				});
13441 			}
13442 		};
13443 
13444 		self.onKeyUp.add(function(ed, e) {
13445 			handleShortcut(e);
13446 		});
13447 
13448 		self.onKeyPress.add(function(ed, e) {
13449 			handleShortcut(e);
13450 		});
13451 
13452 		self.onKeyDown.add(function(ed, e) {
13453 			handleShortcut(e, true);
13454 		});
13455 
13456 		if (tinymce.isOpera) {
13457 			self.onClick.add(function(ed, e) {
13458 				e.preventDefault();
13459 			});
13460 		}
13461 	};
13462 })(tinymce);
13463 (function(tinymce) {
13464 	// Added for compression purposes
13465 	var each = tinymce.each, undef, TRUE = true, FALSE = false;
13466 
13467 	tinymce.EditorCommands = function(editor) {
13468 		var dom = editor.dom,
13469 			selection = editor.selection,
13470 			commands = {state: {}, exec : {}, value : {}},
13471 			settings = editor.settings,
13472 			formatter = editor.formatter,
13473 			bookmark;
13474 
13475 		function execCommand(command, ui, value) {
13476 			var func;
13477 
13478 			command = command.toLowerCase();
13479 			if (func = commands.exec[command]) {
13480 				func(command, ui, value);
13481 				return TRUE;
13482 			}
13483 
13484 			return FALSE;
13485 		};
13486 
13487 		function queryCommandState(command) {
13488 			var func;
13489 
13490 			command = command.toLowerCase();
13491 			if (func = commands.state[command])
13492 				return func(command);
13493 
13494 			return -1;
13495 		};
13496 
13497 		function queryCommandValue(command) {
13498 			var func;
13499 
13500 			command = command.toLowerCase();
13501 			if (func = commands.value[command])
13502 				return func(command);
13503 
13504 			return FALSE;
13505 		};
13506 
13507 		function addCommands(command_list, type) {
13508 			type = type || 'exec';
13509 
13510 			each(command_list, function(callback, command) {
13511 				each(command.toLowerCase().split(','), function(command) {
13512 					commands[type][command] = callback;
13513 				});
13514 			});
13515 		};
13516 
13517 		// Expose public methods
13518 		tinymce.extend(this, {
13519 			execCommand : execCommand,
13520 			queryCommandState : queryCommandState,
13521 			queryCommandValue : queryCommandValue,
13522 			addCommands : addCommands
13523 		});
13524 
13525 		// Private methods
13526 
13527 		function execNativeCommand(command, ui, value) {
13528 			if (ui === undef)
13529 				ui = FALSE;
13530 
13531 			if (value === undef)
13532 				value = null;
13533 
13534 			return editor.getDoc().execCommand(command, ui, value);
13535 		};
13536 
13537 		function isFormatMatch(name) {
13538 			return formatter.match(name);
13539 		};
13540 
13541 		function toggleFormat(name, value) {
13542 			formatter.toggle(name, value ? {value : value} : undef);
13543 		};
13544 
13545 		function storeSelection(type) {
13546 			bookmark = selection.getBookmark(type);
13547 		};
13548 
13549 		function restoreSelection() {
13550 			selection.moveToBookmark(bookmark);
13551 		};
13552 
13553 		// Add execCommand overrides
13554 		addCommands({
13555 			// Ignore these, added for compatibility
13556 			'mceResetDesignMode,mceBeginUndoLevel' : function() {},
13557 
13558 			// Add undo manager logic
13559 			'mceEndUndoLevel,mceAddUndoLevel' : function() {
13560 				editor.undoManager.add();
13561 			},
13562 
13563 			'Cut,Copy,Paste' : function(command) {
13564 				var doc = editor.getDoc(), failed;
13565 
13566 				// Try executing the native command
13567 				try {
13568 					execNativeCommand(command);
13569 				} catch (ex) {
13570 					// Command failed
13571 					failed = TRUE;
13572 				}
13573 
13574 				// Present alert message about clipboard access not being available
13575 				if (failed || !doc.queryCommandSupported(command)) {
13576 					if (tinymce.isGecko) {
13577 						editor.windowManager.confirm(editor.getLang('clipboard_msg'), function(state) {
13578 							if (state)
13579 								open('http://www.mozilla.org/editor/midasdemo/securityprefs.html', '_blank');
13580 						});
13581 					} else
13582 						editor.windowManager.alert(editor.getLang('clipboard_no_support'));
13583 				}
13584 			},
13585 
13586 			// Override unlink command
13587 			unlink : function(command) {
13588 				if (selection.isCollapsed())
13589 					selection.select(selection.getNode());
13590 
13591 				execNativeCommand(command);
13592 				selection.collapse(FALSE);
13593 			},
13594 
13595 			// Override justify commands to use the text formatter engine
13596 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13597 				var align = command.substring(7);
13598 
13599 				// Remove all other alignments first
13600 				each('left,center,right,full'.split(','), function(name) {
13601 					if (align != name)
13602 						formatter.remove('align' + name);
13603 				});
13604 
13605 				toggleFormat('align' + align);
13606 				execCommand('mceRepaint');
13607 			},
13608 
13609 			// Override list commands to fix WebKit bug
13610 			'InsertUnorderedList,InsertOrderedList' : function(command) {
13611 				var listElm, listParent;
13612 
13613 				execNativeCommand(command);
13614 
13615 				// WebKit produces lists within block elements so we need to split them
13616 				// we will replace the native list creation logic to custom logic later on
13617 				// TODO: Remove this when the list creation logic is removed
13618 				listElm = dom.getParent(selection.getNode(), 'ol,ul');
13619 				if (listElm) {
13620 					listParent = listElm.parentNode;
13621 
13622 					// If list is within a text block then split that block
13623 					if (/^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
13624 						storeSelection();
13625 						dom.split(listParent, listElm);
13626 						restoreSelection();
13627 					}
13628 				}
13629 			},
13630 
13631 			// Override commands to use the text formatter engine
13632 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13633 				toggleFormat(command);
13634 			},
13635 
13636 			// Override commands to use the text formatter engine
13637 			'ForeColor,HiliteColor,FontName' : function(command, ui, value) {
13638 				toggleFormat(command, value);
13639 			},
13640 
13641 			FontSize : function(command, ui, value) {
13642 				var fontClasses, fontSizes;
13643 
13644 				// Convert font size 1-7 to styles
13645 				if (value >= 1 && value <= 7) {
13646 					fontSizes = tinymce.explode(settings.font_size_style_values);
13647 					fontClasses = tinymce.explode(settings.font_size_classes);
13648 
13649 					if (fontClasses)
13650 						value = fontClasses[value - 1] || value;
13651 					else
13652 						value = fontSizes[value - 1] || value;
13653 				}
13654 
13655 				toggleFormat(command, value);
13656 			},
13657 
13658 			RemoveFormat : function(command) {
13659 				formatter.remove(command);
13660 			},
13661 
13662 			mceBlockQuote : function(command) {
13663 				toggleFormat('blockquote');
13664 			},
13665 
13666 			FormatBlock : function(command, ui, value) {
13667 				return toggleFormat(value || 'p');
13668 			},
13669 
13670 			mceCleanup : function() {
13671 				var bookmark = selection.getBookmark();
13672 
13673 				editor.setContent(editor.getContent({cleanup : TRUE}), {cleanup : TRUE});
13674 
13675 				selection.moveToBookmark(bookmark);
13676 			},
13677 
13678 			mceRemoveNode : function(command, ui, value) {
13679 				var node = value || selection.getNode();
13680 
13681 				// Make sure that the body node isn't removed
13682 				if (node != editor.getBody()) {
13683 					storeSelection();
13684 					editor.dom.remove(node, TRUE);
13685 					restoreSelection();
13686 				}
13687 			},
13688 
13689 			mceSelectNodeDepth : function(command, ui, value) {
13690 				var counter = 0;
13691 
13692 				dom.getParent(selection.getNode(), function(node) {
13693 					if (node.nodeType == 1 && counter++ == value) {
13694 						selection.select(node);
13695 						return FALSE;
13696 					}
13697 				}, editor.getBody());
13698 			},
13699 
13700 			mceSelectNode : function(command, ui, value) {
13701 				selection.select(value);
13702 			},
13703 
13704 			mceInsertContent : function(command, ui, value) {
13705 				var parser, serializer, parentNode, rootNode, fragment, args,
13706 					marker, nodeRect, viewPortRect, rng, node, node2, bookmarkHtml, viewportBodyElement;
13707 
13708 				//selection.normalize();
13709 
13710 				// Setup parser and serializer
13711 				parser = editor.parser;
13712 				serializer = new tinymce.html.Serializer({}, editor.schema);
13713 				bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">\uFEFF</span>';
13714 
13715 				// Run beforeSetContent handlers on the HTML to be inserted
13716 				args = {content: value, format: 'html'};
13717 				selection.onBeforeSetContent.dispatch(selection, args);
13718 				value = args.content;
13719 
13720 				// Add caret at end of contents if it's missing
13721 				if (value.indexOf('{$caret}') == -1)
13722 					value += '{$caret}';
13723 
13724 				// Replace the caret marker with a span bookmark element
13725 				value = value.replace(/\{\$caret\}/, bookmarkHtml);
13726 
13727 				// Insert node maker where we will insert the new HTML and get it's parent
13728 				if (!selection.isCollapsed())
13729 					editor.getDoc().execCommand('Delete', false, null);
13730 
13731 				parentNode = selection.getNode();
13732 
13733 				// Parse the fragment within the context of the parent node
13734 				args = {context : parentNode.nodeName.toLowerCase()};
13735 				fragment = parser.parse(value, args);
13736 
13737 				// Move the caret to a more suitable location
13738 				node = fragment.lastChild;
13739 				if (node.attr('id') == 'mce_marker') {
13740 					marker = node;
13741 
13742 					for (node = node.prev; node; node = node.walk(true)) {
13743 						if (node.type == 3 || !dom.isBlock(node.name)) {
13744 							node.parent.insert(marker, node, node.name === 'br');
13745 							break;
13746 						}
13747 					}
13748 				}
13749 
13750 				// If parser says valid we can insert the contents into that parent
13751 				if (!args.invalid) {
13752 					value = serializer.serialize(fragment);
13753 
13754 					// Check if parent is empty or only has one BR element then set the innerHTML of that parent
13755 					node = parentNode.firstChild;
13756 					node2 = parentNode.lastChild;
13757 					if (!node || (node === node2 && node.nodeName === 'BR'))
13758 						dom.setHTML(parentNode, value);
13759 					else
13760 						selection.setContent(value);
13761 				} else {
13762 					// If the fragment was invalid within that context then we need
13763 					// to parse and process the parent it's inserted into
13764 
13765 					// Insert bookmark node and get the parent
13766 					selection.setContent(bookmarkHtml);
13767 					parentNode = editor.selection.getNode();
13768 					rootNode = editor.getBody();
13769 
13770 					// Opera will return the document node when selection is in root
13771 					if (parentNode.nodeType == 9)
13772 						parentNode = node = rootNode;
13773 					else
13774 						node = parentNode;
13775 
13776 					// Find the ancestor just before the root element
13777 					while (node !== rootNode) {
13778 						parentNode = node;
13779 						node = node.parentNode;
13780 					}
13781 
13782 					// Get the outer/inner HTML depending on if we are in the root and parser and serialize that
13783 					value = parentNode == rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
13784 					value = serializer.serialize(
13785 						parser.parse(
13786 							// Need to replace by using a function since $ in the contents would otherwise be a problem
13787 							value.replace(/<span (id="mce_marker"|id=mce_marker).+?<\/span>/i, function() {
13788 								return serializer.serialize(fragment);
13789 							})
13790 						)
13791 					);
13792 
13793 					// Set the inner/outer HTML depending on if we are in the root or not
13794 					if (parentNode == rootNode)
13795 						dom.setHTML(rootNode, value);
13796 					else
13797 						dom.setOuterHTML(parentNode, value);
13798 				}
13799 
13800 				marker = dom.get('mce_marker');
13801 
13802 				// Scroll range into view scrollIntoView on element can't be used since it will scroll the main view port as well
13803 				nodeRect = dom.getRect(marker);
13804 				viewPortRect = dom.getViewPort(editor.getWin());
13805 
13806 				// Check if node is out side the viewport if it is then scroll to it
13807 				if ((nodeRect.y + nodeRect.h > viewPortRect.y + viewPortRect.h || nodeRect.y < viewPortRect.y) ||
13808 					(nodeRect.x > viewPortRect.x + viewPortRect.w || nodeRect.x < viewPortRect.x)) {
13809 					viewportBodyElement = tinymce.isIE ? editor.getDoc().documentElement : editor.getBody();
13810 					viewportBodyElement.scrollLeft = nodeRect.x;
13811 					viewportBodyElement.scrollTop = nodeRect.y - viewPortRect.h + 25;
13812 				}
13813 
13814 				// Move selection before marker and remove it
13815 				rng = dom.createRng();
13816 
13817 				// If previous sibling is a text node set the selection to the end of that node
13818 				node = marker.previousSibling;
13819 				if (node && node.nodeType == 3) {
13820 					rng.setStart(node, node.nodeValue.length);
13821 				} else {
13822 					// If the previous sibling isn't a text node or doesn't exist set the selection before the marker node
13823 					rng.setStartBefore(marker);
13824 					rng.setEndBefore(marker);
13825 				}
13826 
13827 				// Remove the marker node and set the new range
13828 				dom.remove(marker);
13829 				selection.setRng(rng);
13830 
13831 				// Dispatch after event and add any visual elements needed
13832 				selection.onSetContent.dispatch(selection, args);
13833 				editor.addVisual();
13834 			},
13835 
13836 			mceInsertRawHTML : function(command, ui, value) {
13837 				selection.setContent('tiny_mce_marker');
13838 				editor.setContent(editor.getContent().replace(/tiny_mce_marker/g, function() { return value }));
13839 			},
13840 
13841 			mceToggleFormat : function(command, ui, value) {
13842 				toggleFormat(value);
13843 			},
13844 
13845 			mceSetContent : function(command, ui, value) {
13846 				editor.setContent(value);
13847 			},
13848 
13849 			'Indent,Outdent' : function(command) {
13850 				var intentValue, indentUnit, value;
13851 
13852 				// Setup indent level
13853 				intentValue = settings.indentation;
13854 				indentUnit = /[a-z%]+$/i.exec(intentValue);
13855 				intentValue = parseInt(intentValue);
13856 
13857 				if (!queryCommandState('InsertUnorderedList') && !queryCommandState('InsertOrderedList')) {
13858 					// If forced_root_blocks is set to false we don't have a block to indent so lets create a div
13859 					if (!settings.forced_root_block && !dom.getParent(selection.getNode(), dom.isBlock)) {
13860 						formatter.apply('div');
13861 					}
13862 
13863 					each(selection.getSelectedBlocks(), function(element) {
13864 						if (command == 'outdent') {
13865 							value = Math.max(0, parseInt(element.style.paddingLeft || 0) - intentValue);
13866 							dom.setStyle(element, 'paddingLeft', value ? value + indentUnit : '');
13867 						} else
13868 							dom.setStyle(element, 'paddingLeft', (parseInt(element.style.paddingLeft || 0) + intentValue) + indentUnit);
13869 					});
13870 				} else
13871 					execNativeCommand(command);
13872 			},
13873 
13874 			mceRepaint : function() {
13875 				var bookmark;
13876 
13877 				if (tinymce.isGecko) {
13878 					try {
13879 						storeSelection(TRUE);
13880 
13881 						if (selection.getSel())
13882 							selection.getSel().selectAllChildren(editor.getBody());
13883 
13884 						selection.collapse(TRUE);
13885 						restoreSelection();
13886 					} catch (ex) {
13887 						// Ignore
13888 					}
13889 				}
13890 			},
13891 
13892 			mceToggleFormat : function(command, ui, value) {
13893 				formatter.toggle(value);
13894 			},
13895 
13896 			InsertHorizontalRule : function() {
13897 				editor.execCommand('mceInsertContent', false, '<hr />');
13898 			},
13899 
13900 			mceToggleVisualAid : function() {
13901 				editor.hasVisual = !editor.hasVisual;
13902 				editor.addVisual();
13903 			},
13904 
13905 			mceReplaceContent : function(command, ui, value) {
13906 				editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, selection.getContent({format : 'text'})));
13907 			},
13908 
13909 			mceInsertLink : function(command, ui, value) {
13910 				var anchor;
13911 
13912 				if (typeof(value) == 'string')
13913 					value = {href : value};
13914 
13915 				anchor = dom.getParent(selection.getNode(), 'a');
13916 
13917 				// Spaces are never valid in URLs and it's a very common mistake for people to make so we fix it here.
13918 				value.href = value.href.replace(' ', '%20');
13919 
13920 				// Remove existing links if there could be child links or that the href isn't specified
13921 				if (!anchor || !value.href) {
13922 					formatter.remove('link');
13923 				}		
13924 
13925 				// Apply new link to selection
13926 				if (value.href) {
13927 					formatter.apply('link', value, anchor);
13928 				}
13929 			},
13930 
13931 			selectAll : function() {
13932 				var root = dom.getRoot(), rng = dom.createRng();
13933 
13934 				rng.setStart(root, 0);
13935 				rng.setEnd(root, root.childNodes.length);
13936 
13937 				editor.selection.setRng(rng);
13938 			}
13939 		});
13940 
13941 		// Add queryCommandState overrides
13942 		addCommands({
13943 			// Override justify commands
13944 			'JustifyLeft,JustifyCenter,JustifyRight,JustifyFull' : function(command) {
13945 				var name = 'align' + command.substring(7);
13946 				var nodes = selection.isCollapsed() ? [dom.getParent(selection.getNode(), dom.isBlock)] : selection.getSelectedBlocks();
13947 				var matches = tinymce.map(nodes, function(node) {
13948 					return !!formatter.matchNode(node, name);
13949 				});
13950 				return tinymce.inArray(matches, TRUE) !== -1;
13951 			},
13952 
13953 			'Bold,Italic,Underline,Strikethrough,Superscript,Subscript' : function(command) {
13954 				return isFormatMatch(command);
13955 			},
13956 
13957 			mceBlockQuote : function() {
13958 				return isFormatMatch('blockquote');
13959 			},
13960 
13961 			Outdent : function() {
13962 				var node;
13963 
13964 				if (settings.inline_styles) {
13965 					if ((node = dom.getParent(selection.getStart(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13966 						return TRUE;
13967 
13968 					if ((node = dom.getParent(selection.getEnd(), dom.isBlock)) && parseInt(node.style.paddingLeft) > 0)
13969 						return TRUE;
13970 				}
13971 
13972 				return queryCommandState('InsertUnorderedList') || queryCommandState('InsertOrderedList') || (!settings.inline_styles && !!dom.getParent(selection.getNode(), 'BLOCKQUOTE'));
13973 			},
13974 
13975 			'InsertUnorderedList,InsertOrderedList' : function(command) {
13976 				return dom.getParent(selection.getNode(), command == 'insertunorderedlist' ? 'UL' : 'OL');
13977 			}
13978 		}, 'state');
13979 
13980 		// Add queryCommandValue overrides
13981 		addCommands({
13982 			'FontSize,FontName' : function(command) {
13983 				var value = 0, parent;
13984 
13985 				if (parent = dom.getParent(selection.getNode(), 'span')) {
13986 					if (command == 'fontsize')
13987 						value = parent.style.fontSize;
13988 					else
13989 						value = parent.style.fontFamily.replace(/, /g, ',').replace(/[\'\"]/g, '').toLowerCase();
13990 				}
13991 
13992 				return value;
13993 			}
13994 		}, 'value');
13995 
13996 		// Add undo manager logic
13997 		addCommands({
13998 			Undo : function() {
13999 				editor.undoManager.undo();
14000 			},
14001 
14002 			Redo : function() {
14003 				editor.undoManager.redo();
14004 			}
14005 		});
14006 	};
14007 })(tinymce);
14008 
14009 (function(tinymce) {
14010 	var Dispatcher = tinymce.util.Dispatcher;
14011 
14012 	tinymce.UndoManager = function(editor) {
14013 		var self, index = 0, data = [], beforeBookmark, onAdd, onUndo, onRedo;
14014 
14015 		function getContent() {
14016 			// Remove whitespace before/after and remove pure bogus nodes
14017 			return tinymce.trim(editor.getContent({format : 'raw', no_events : 1}).replace(/<span[^>]+data-mce-bogus[^>]+>[\u200B\uFEFF]+<\/span>/g, ''));
14018 		};
14019 
14020 		function addNonTypingUndoLevel() {
14021 			self.typing = false;
14022 			self.add();
14023 		};
14024 
14025 		// Create event instances
14026 		onBeforeAdd = new Dispatcher(self);
14027 		onAdd       = new Dispatcher(self);
14028 		onUndo      = new Dispatcher(self);
14029 		onRedo      = new Dispatcher(self);
14030 
14031 		// Pass though onAdd event from UndoManager to Editor as onChange
14032 		onAdd.add(function(undoman, level) {
14033 			if (undoman.hasUndo())
14034 				return editor.onChange.dispatch(editor, level, undoman);
14035 		});
14036 
14037 		// Pass though onUndo event from UndoManager to Editor
14038 		onUndo.add(function(undoman, level) {
14039 			return editor.onUndo.dispatch(editor, level, undoman);
14040 		});
14041 
14042 		// Pass though onRedo event from UndoManager to Editor
14043 		onRedo.add(function(undoman, level) {
14044 			return editor.onRedo.dispatch(editor, level, undoman);
14045 		});
14046 
14047 		// Add initial undo level when the editor is initialized
14048 		editor.onInit.add(function() {
14049 			self.add();
14050 		});
14051 
14052 		// Get position before an execCommand is processed
14053 		editor.onBeforeExecCommand.add(function(ed, cmd, ui, val, args) {
14054 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
14055 				self.beforeChange();
14056 			}
14057 		});
14058 
14059 		// Add undo level after an execCommand call was made
14060 		editor.onExecCommand.add(function(ed, cmd, ui, val, args) {
14061 			if (cmd != 'Undo' && cmd != 'Redo' && cmd != 'mceRepaint' && (!args || !args.skip_undo)) {
14062 				self.add();
14063 			}
14064 		});
14065 
14066 		// Add undo level on save contents, drag end and blur/focusout
14067 		editor.onSaveContent.add(addNonTypingUndoLevel);
14068 		editor.dom.bind(editor.dom.getRoot(), 'dragend', addNonTypingUndoLevel);
14069 		editor.dom.bind(editor.getDoc(), tinymce.isGecko ? 'blur' : 'focusout', function(e) {
14070 			if (!editor.removed && self.typing) {
14071 				addNonTypingUndoLevel();
14072 			}
14073 		});
14074 
14075 		editor.onKeyUp.add(function(editor, e) {
14076 			var keyCode = e.keyCode;
14077 
14078 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45 || keyCode == 13 || e.ctrlKey) {
14079 				addNonTypingUndoLevel();
14080 			}
14081 		});
14082 
14083 		editor.onKeyDown.add(function(editor, e) {
14084 			var keyCode = e.keyCode;
14085 
14086 			// Is caracter positon keys left,right,up,down,home,end,pgdown,pgup,enter
14087 			if ((keyCode >= 33 && keyCode <= 36) || (keyCode >= 37 && keyCode <= 40) || keyCode == 45) {
14088 				if (self.typing) {
14089 					addNonTypingUndoLevel();
14090 				}
14091 
14092 				return;
14093 			}
14094 
14095 			// If key isn't shift,ctrl,alt,capslock,metakey
14096 			if ((keyCode < 16 || keyCode > 20) && keyCode != 224 && keyCode != 91 && !self.typing) {
14097 				self.beforeChange();
14098 				self.typing = true;
14099 				self.add();
14100 			}
14101 		});
14102 
14103 		editor.onMouseDown.add(function(editor, e) {
14104 			if (self.typing) {
14105 				addNonTypingUndoLevel();
14106 			}
14107 		});
14108 
14109 		// Add keyboard shortcuts for undo/redo keys
14110 		editor.addShortcut('ctrl+z', 'undo_desc', 'Undo');
14111 		editor.addShortcut('ctrl+y', 'redo_desc', 'Redo');
14112 
14113 		self = {
14114 			// Explose for debugging reasons
14115 			data : data,
14116 
14117 			typing : false,
14118 			
14119 			onBeforeAdd: onBeforeAdd,
14120 
14121 			onAdd : onAdd,
14122 
14123 			onUndo : onUndo,
14124 
14125 			onRedo : onRedo,
14126 
14127 			beforeChange : function() {
14128 				beforeBookmark = editor.selection.getBookmark(2, true);
14129 			},
14130 
14131 			add : function(level) {
14132 				var i, settings = editor.settings, lastLevel;
14133 
14134 				level = level || {};
14135 				level.content = getContent();
14136 				
14137 				self.onBeforeAdd.dispatch(self, level);
14138 
14139 				// Add undo level if needed
14140 				lastLevel = data[index];
14141 				if (lastLevel && lastLevel.content == level.content)
14142 					return null;
14143 
14144 				// Set before bookmark on previous level
14145 				if (data[index])
14146 					data[index].beforeBookmark = beforeBookmark;
14147 
14148 				// Time to compress
14149 				if (settings.custom_undo_redo_levels) {
14150 					if (data.length > settings.custom_undo_redo_levels) {
14151 						for (i = 0; i < data.length - 1; i++)
14152 							data[i] = data[i + 1];
14153 
14154 						data.length--;
14155 						index = data.length;
14156 					}
14157 				}
14158 
14159 				// Get a non intrusive normalized bookmark
14160 				level.bookmark = editor.selection.getBookmark(2, true);
14161 
14162 				// Crop array if needed
14163 				if (index < data.length - 1)
14164 					data.length = index + 1;
14165 
14166 				data.push(level);
14167 				index = data.length - 1;
14168 
14169 				self.onAdd.dispatch(self, level);
14170 				editor.isNotDirty = 0;
14171 
14172 				return level;
14173 			},
14174 
14175 			undo : function() {
14176 				var level, i;
14177 
14178 				if (self.typing) {
14179 					self.add();
14180 					self.typing = false;
14181 				}
14182 
14183 				if (index > 0) {
14184 					level = data[--index];
14185 
14186 					editor.setContent(level.content, {format : 'raw'});
14187 					editor.selection.moveToBookmark(level.beforeBookmark);
14188 
14189 					self.onUndo.dispatch(self, level);
14190 				}
14191 
14192 				return level;
14193 			},
14194 
14195 			redo : function() {
14196 				var level;
14197 
14198 				if (index < data.length - 1) {
14199 					level = data[++index];
14200 
14201 					editor.setContent(level.content, {format : 'raw'});
14202 					editor.selection.moveToBookmark(level.bookmark);
14203 
14204 					self.onRedo.dispatch(self, level);
14205 				}
14206 
14207 				return level;
14208 			},
14209 
14210 			clear : function() {
14211 				data = [];
14212 				index = 0;
14213 				self.typing = false;
14214 			},
14215 
14216 			hasUndo : function() {
14217 				return index > 0 || this.typing;
14218 			},
14219 
14220 			hasRedo : function() {
14221 				return index < data.length - 1 && !this.typing;
14222 			}
14223 		};
14224 
14225 		return self;
14226 	};
14227 })(tinymce);
14228 
14229 tinymce.ForceBlocks = function(editor) {
14230 	var settings = editor.settings, dom = editor.dom, selection = editor.selection, blockElements = editor.schema.getBlockElements();
14231 
14232 	function addRootBlocks() {
14233 		var node = selection.getStart(), rootNode = editor.getBody(), rng, startContainer, startOffset, endContainer, endOffset, rootBlockNode, tempNode, offset = -0xFFFFFF, wrapped, isInEditorDocument;
14234 
14235 		if (!node || node.nodeType !== 1 || !settings.forced_root_block)
14236 			return;
14237 
14238 		// Check if node is wrapped in block
14239 		while (node && node != rootNode) {
14240 			if (blockElements[node.nodeName])
14241 				return;
14242 
14243 			node = node.parentNode;
14244 		}
14245 
14246 		// Get current selection
14247 		rng = selection.getRng();
14248 		if (rng.setStart) {
14249 			startContainer = rng.startContainer;
14250 			startOffset = rng.startOffset;
14251 			endContainer = rng.endContainer;
14252 			endOffset = rng.endOffset;
14253 		} else {
14254 			// Force control range into text range
14255 			if (rng.item) {
14256 				node = rng.item(0);
14257 				rng = editor.getDoc().body.createTextRange();
14258 				rng.moveToElementText(node);
14259 			}
14260 
14261 			isInEditorDocument = rng.parentElement().ownerDocument === editor.getDoc();
14262 			tmpRng = rng.duplicate();
14263 			tmpRng.collapse(true);
14264 			startOffset = tmpRng.move('character', offset) * -1;
14265 
14266 			if (!tmpRng.collapsed) {
14267 				tmpRng = rng.duplicate();
14268 				tmpRng.collapse(false);
14269 				endOffset = (tmpRng.move('character', offset) * -1) - startOffset;
14270 			}
14271 		}
14272 
14273 		// Wrap non block elements and text nodes
14274 		node = rootNode.firstChild;
14275 		while (node) {
14276 			if (node.nodeType === 3 || (node.nodeType == 1 && !blockElements[node.nodeName])) {
14277 				if (!rootBlockNode) {
14278 					rootBlockNode = dom.create(settings.forced_root_block);
14279 					node.parentNode.insertBefore(rootBlockNode, node);
14280 					wrapped = true;
14281 				}
14282 
14283 				tempNode = node;
14284 				node = node.nextSibling;
14285 				rootBlockNode.appendChild(tempNode);
14286 			} else {
14287 				rootBlockNode = null;
14288 				node = node.nextSibling;
14289 			}
14290 		}
14291 
14292 		if (wrapped) {
14293 			if (rng.setStart) {
14294 				rng.setStart(startContainer, startOffset);
14295 				rng.setEnd(endContainer, endOffset);
14296 				selection.setRng(rng);
14297 			} else {
14298 				// Only select if the previous selection was inside the document to prevent auto focus in quirks mode
14299 				if (isInEditorDocument) {
14300 					try {
14301 						rng = editor.getDoc().body.createTextRange();
14302 						rng.moveToElementText(rootNode);
14303 						rng.collapse(true);
14304 						rng.moveStart('character', startOffset);
14305 
14306 						if (endOffset > 0)
14307 							rng.moveEnd('character', endOffset);
14308 
14309 						rng.select();
14310 					} catch (ex) {
14311 						// Ignore
14312 					}
14313 				}
14314 			}
14315 
14316 			editor.nodeChanged();
14317 		}
14318 	};
14319 
14320 	// Force root blocks
14321 	if (settings.forced_root_block) {
14322 		editor.onKeyUp.add(addRootBlocks);
14323 		editor.onNodeChange.add(addRootBlocks);
14324 	}
14325 };
14326 
14327 (function(tinymce) {
14328 	// Shorten names
14329 	var DOM = tinymce.DOM, Event = tinymce.dom.Event, each = tinymce.each, extend = tinymce.extend;
14330 
14331 	tinymce.create('tinymce.ControlManager', {
14332 		ControlManager : function(ed, s) {
14333 			var t = this, i;
14334 
14335 			s = s || {};
14336 			t.editor = ed;
14337 			t.controls = {};
14338 			t.onAdd = new tinymce.util.Dispatcher(t);
14339 			t.onPostRender = new tinymce.util.Dispatcher(t);
14340 			t.prefix = s.prefix || ed.id + '_';
14341 			t._cls = {};
14342 
14343 			t.onPostRender.add(function() {
14344 				each(t.controls, function(c) {
14345 					c.postRender();
14346 				});
14347 			});
14348 		},
14349 
14350 		get : function(id) {
14351 			return this.controls[this.prefix + id] || this.controls[id];
14352 		},
14353 
14354 		setActive : function(id, s) {
14355 			var c = null;
14356 
14357 			if (c = this.get(id))
14358 				c.setActive(s);
14359 
14360 			return c;
14361 		},
14362 
14363 		setDisabled : function(id, s) {
14364 			var c = null;
14365 
14366 			if (c = this.get(id))
14367 				c.setDisabled(s);
14368 
14369 			return c;
14370 		},
14371 
14372 		add : function(c) {
14373 			var t = this;
14374 
14375 			if (c) {
14376 				t.controls[c.id] = c;
14377 				t.onAdd.dispatch(c, t);
14378 			}
14379 
14380 			return c;
14381 		},
14382 
14383 		createControl : function(name) {
14384 			var ctrl, i, l, self = this, editor = self.editor, factories, ctrlName;
14385 
14386 			// Build control factory cache
14387 			if (!self.controlFactories) {
14388 				self.controlFactories = [];
14389 				each(editor.plugins, function(plugin) {
14390 					if (plugin.createControl) {
14391 						self.controlFactories.push(plugin);
14392 					}
14393 				});
14394 			}
14395 
14396 			// Create controls by asking cached factories
14397 			factories = self.controlFactories;
14398 			for (i = 0, l = factories.length; i < l; i++) {
14399 				ctrl = factories[i].createControl(name, self);
14400 
14401 				if (ctrl) {
14402 					return self.add(ctrl);
14403 				}
14404 			}
14405 
14406 			// Create sepearator
14407 			if (name === "|" || name === "separator") {
14408 				return self.createSeparator();
14409 			}
14410 
14411 			// Create control from button collection
14412 			if (editor.buttons && (ctrl = editor.buttons[name])) {
14413 				return self.createButton(name, ctrl);
14414 			}
14415 
14416 			return self.add(ctrl);
14417 		},
14418 
14419 		createDropMenu : function(id, s, cc) {
14420 			var t = this, ed = t.editor, c, bm, v, cls;
14421 
14422 			s = extend({
14423 				'class' : 'mceDropDown',
14424 				constrain : ed.settings.constrain_menus
14425 			}, s);
14426 
14427 			s['class'] = s['class'] + ' ' + ed.getParam('skin') + 'Skin';
14428 			if (v = ed.getParam('skin_variant'))
14429 				s['class'] += ' ' + ed.getParam('skin') + 'Skin' + v.substring(0, 1).toUpperCase() + v.substring(1);
14430 
14431 			s['class'] += ed.settings.directionality == "rtl" ? ' mceRtl' : '';
14432 
14433 			id = t.prefix + id;
14434 			cls = cc || t._cls.dropmenu || tinymce.ui.DropMenu;
14435 			c = t.controls[id] = new cls(id, s);
14436 			c.onAddItem.add(function(c, o) {
14437 				var s = o.settings;
14438 
14439 				s.title = ed.getLang(s.title, s.title);
14440 
14441 				if (!s.onclick) {
14442 					s.onclick = function(v) {
14443 						if (s.cmd)
14444 							ed.execCommand(s.cmd, s.ui || false, s.value);
14445 					};
14446 				}
14447 			});
14448 
14449 			ed.onRemove.add(function() {
14450 				c.destroy();
14451 			});
14452 
14453 			// Fix for bug #1897785, #1898007
14454 			if (tinymce.isIE) {
14455 				c.onShowMenu.add(function() {
14456 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
14457 					ed.focus();
14458 
14459 					bm = ed.selection.getBookmark(1);
14460 				});
14461 
14462 				c.onHideMenu.add(function() {
14463 					if (bm) {
14464 						ed.selection.moveToBookmark(bm);
14465 						bm = 0;
14466 					}
14467 				});
14468 			}
14469 
14470 			return t.add(c);
14471 		},
14472 
14473 		createListBox : function(id, s, cc) {
14474 			var t = this, ed = t.editor, cmd, c, cls;
14475 
14476 			if (t.get(id))
14477 				return null;
14478 
14479 			s.title = ed.translate(s.title);
14480 			s.scope = s.scope || ed;
14481 
14482 			if (!s.onselect) {
14483 				s.onselect = function(v) {
14484 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14485 				};
14486 			}
14487 
14488 			s = extend({
14489 				title : s.title,
14490 				'class' : 'mce_' + id,
14491 				scope : s.scope,
14492 				control_manager : t
14493 			}, s);
14494 
14495 			id = t.prefix + id;
14496 
14497 
14498 			function useNativeListForAccessibility(ed) {
14499 				return ed.settings.use_accessible_selects && !tinymce.isGecko
14500 			}
14501 
14502 			if (ed.settings.use_native_selects || useNativeListForAccessibility(ed))
14503 				c = new tinymce.ui.NativeListBox(id, s);
14504 			else {
14505 				cls = cc || t._cls.listbox || tinymce.ui.ListBox;
14506 				c = new cls(id, s, ed);
14507 			}
14508 
14509 			t.controls[id] = c;
14510 
14511 			// Fix focus problem in Safari
14512 			if (tinymce.isWebKit) {
14513 				c.onPostRender.add(function(c, n) {
14514 					// Store bookmark on mousedown
14515 					Event.add(n, 'mousedown', function() {
14516 						ed.bookmark = ed.selection.getBookmark(1);
14517 					});
14518 
14519 					// Restore on focus, since it might be lost
14520 					Event.add(n, 'focus', function() {
14521 						ed.selection.moveToBookmark(ed.bookmark);
14522 						ed.bookmark = null;
14523 					});
14524 				});
14525 			}
14526 
14527 			if (c.hideMenu)
14528 				ed.onMouseDown.add(c.hideMenu, c);
14529 
14530 			return t.add(c);
14531 		},
14532 
14533 		createButton : function(id, s, cc) {
14534 			var t = this, ed = t.editor, o, c, cls;
14535 
14536 			if (t.get(id))
14537 				return null;
14538 
14539 			s.title = ed.translate(s.title);
14540 			s.label = ed.translate(s.label);
14541 			s.scope = s.scope || ed;
14542 
14543 			if (!s.onclick && !s.menu_button) {
14544 				s.onclick = function() {
14545 					ed.execCommand(s.cmd, s.ui || false, s.value);
14546 				};
14547 			}
14548 
14549 			s = extend({
14550 				title : s.title,
14551 				'class' : 'mce_' + id,
14552 				unavailable_prefix : ed.getLang('unavailable', ''),
14553 				scope : s.scope,
14554 				control_manager : t
14555 			}, s);
14556 
14557 			id = t.prefix + id;
14558 
14559 			if (s.menu_button) {
14560 				cls = cc || t._cls.menubutton || tinymce.ui.MenuButton;
14561 				c = new cls(id, s, ed);
14562 				ed.onMouseDown.add(c.hideMenu, c);
14563 			} else {
14564 				cls = t._cls.button || tinymce.ui.Button;
14565 				c = new cls(id, s, ed);
14566 			}
14567 
14568 			return t.add(c);
14569 		},
14570 
14571 		createMenuButton : function(id, s, cc) {
14572 			s = s || {};
14573 			s.menu_button = 1;
14574 
14575 			return this.createButton(id, s, cc);
14576 		},
14577 
14578 		createSplitButton : function(id, s, cc) {
14579 			var t = this, ed = t.editor, cmd, c, cls;
14580 
14581 			if (t.get(id))
14582 				return null;
14583 
14584 			s.title = ed.translate(s.title);
14585 			s.scope = s.scope || ed;
14586 
14587 			if (!s.onclick) {
14588 				s.onclick = function(v) {
14589 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14590 				};
14591 			}
14592 
14593 			if (!s.onselect) {
14594 				s.onselect = function(v) {
14595 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14596 				};
14597 			}
14598 
14599 			s = extend({
14600 				title : s.title,
14601 				'class' : 'mce_' + id,
14602 				scope : s.scope,
14603 				control_manager : t
14604 			}, s);
14605 
14606 			id = t.prefix + id;
14607 			cls = cc || t._cls.splitbutton || tinymce.ui.SplitButton;
14608 			c = t.add(new cls(id, s, ed));
14609 			ed.onMouseDown.add(c.hideMenu, c);
14610 
14611 			return c;
14612 		},
14613 
14614 		createColorSplitButton : function(id, s, cc) {
14615 			var t = this, ed = t.editor, cmd, c, cls, bm;
14616 
14617 			if (t.get(id))
14618 				return null;
14619 
14620 			s.title = ed.translate(s.title);
14621 			s.scope = s.scope || ed;
14622 
14623 			if (!s.onclick) {
14624 				s.onclick = function(v) {
14625 					if (tinymce.isIE)
14626 						bm = ed.selection.getBookmark(1);
14627 
14628 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14629 				};
14630 			}
14631 
14632 			if (!s.onselect) {
14633 				s.onselect = function(v) {
14634 					ed.execCommand(s.cmd, s.ui || false, v || s.value);
14635 				};
14636 			}
14637 
14638 			s = extend({
14639 				title : s.title,
14640 				'class' : 'mce_' + id,
14641 				'menu_class' : ed.getParam('skin') + 'Skin',
14642 				scope : s.scope,
14643 				more_colors_title : ed.getLang('more_colors')
14644 			}, s);
14645 
14646 			id = t.prefix + id;
14647 			cls = cc || t._cls.colorsplitbutton || tinymce.ui.ColorSplitButton;
14648 			c = new cls(id, s, ed);
14649 			ed.onMouseDown.add(c.hideMenu, c);
14650 
14651 			// Remove the menu element when the editor is removed
14652 			ed.onRemove.add(function() {
14653 				c.destroy();
14654 			});
14655 
14656 			// Fix for bug #1897785, #1898007
14657 			if (tinymce.isIE) {
14658 				c.onShowMenu.add(function() {
14659 					// IE 8 needs focus in order to store away a range with the current collapsed caret location
14660 					ed.focus();
14661 					bm = ed.selection.getBookmark(1);
14662 				});
14663 
14664 				c.onHideMenu.add(function() {
14665 					if (bm) {
14666 						ed.selection.moveToBookmark(bm);
14667 						bm = 0;
14668 					}
14669 				});
14670 			}
14671 
14672 			return t.add(c);
14673 		},
14674 
14675 		createToolbar : function(id, s, cc) {
14676 			var c, t = this, cls;
14677 
14678 			id = t.prefix + id;
14679 			cls = cc || t._cls.toolbar || tinymce.ui.Toolbar;
14680 			c = new cls(id, s, t.editor);
14681 
14682 			if (t.get(id))
14683 				return null;
14684 
14685 			return t.add(c);
14686 		},
14687 		
14688 		createToolbarGroup : function(id, s, cc) {
14689 			var c, t = this, cls;
14690 			id = t.prefix + id;
14691 			cls = cc || this._cls.toolbarGroup || tinymce.ui.ToolbarGroup;
14692 			c = new cls(id, s, t.editor);
14693 			
14694 			if (t.get(id))
14695 				return null;
14696 			
14697 			return t.add(c);
14698 		},
14699 
14700 		createSeparator : function(cc) {
14701 			var cls = cc || this._cls.separator || tinymce.ui.Separator;
14702 
14703 			return new cls();
14704 		},
14705 
14706 		setControlType : function(n, c) {
14707 			return this._cls[n.toLowerCase()] = c;
14708 		},
14709 	
14710 		destroy : function() {
14711 			each(this.controls, function(c) {
14712 				c.destroy();
14713 			});
14714 
14715 			this.controls = null;
14716 		}
14717 	});
14718 })(tinymce);
14719 
14720 (function(tinymce) {
14721 	var Dispatcher = tinymce.util.Dispatcher, each = tinymce.each, isIE = tinymce.isIE, isOpera = tinymce.isOpera;
14722 
14723 	tinymce.create('tinymce.WindowManager', {
14724 		WindowManager : function(ed) {
14725 			var t = this;
14726 
14727 			t.editor = ed;
14728 			t.onOpen = new Dispatcher(t);
14729 			t.onClose = new Dispatcher(t);
14730 			t.params = {};
14731 			t.features = {};
14732 		},
14733 
14734 		open : function(s, p) {
14735 			var t = this, f = '', x, y, mo = t.editor.settings.dialog_type == 'modal', w, sw, sh, vp = tinymce.DOM.getViewPort(), u;
14736 
14737 			// Default some options
14738 			s = s || {};
14739 			p = p || {};
14740 			sw = isOpera ? vp.w : screen.width; // Opera uses windows inside the Opera window
14741 			sh = isOpera ? vp.h : screen.height;
14742 			s.name = s.name || 'mc_' + new Date().getTime();
14743 			s.width = parseInt(s.width || 320);
14744 			s.height = parseInt(s.height || 240);
14745 			s.resizable = true;
14746 			s.left = s.left || parseInt(sw / 2.0) - (s.width / 2.0);
14747 			s.top = s.top || parseInt(sh / 2.0) - (s.height / 2.0);
14748 			p.inline = false;
14749 			p.mce_width = s.width;
14750 			p.mce_height = s.height;
14751 			p.mce_auto_focus = s.auto_focus;
14752 
14753 			if (mo) {
14754 				if (isIE) {
14755 					s.center = true;
14756 					s.help = false;
14757 					s.dialogWidth = s.width + 'px';
14758 					s.dialogHeight = s.height + 'px';
14759 					s.scroll = s.scrollbars || false;
14760 				}
14761 			}
14762 
14763 			// Build features string
14764 			each(s, function(v, k) {
14765 				if (tinymce.is(v, 'boolean'))
14766 					v = v ? 'yes' : 'no';
14767 
14768 				if (!/^(name|url)$/.test(k)) {
14769 					if (isIE && mo)
14770 						f += (f ? ';' : '') + k + ':' + v;
14771 					else
14772 						f += (f ? ',' : '') + k + '=' + v;
14773 				}
14774 			});
14775 
14776 			t.features = s;
14777 			t.params = p;
14778 			t.onOpen.dispatch(t, s, p);
14779 
14780 			u = s.url || s.file;
14781 			u = tinymce._addVer(u);
14782 
14783 			try {
14784 				if (isIE && mo) {
14785 					w = 1;
14786 					window.showModalDialog(u, window, f);
14787 				} else
14788 					w = window.open(u, s.name, f);
14789 			} catch (ex) {
14790 				// Ignore
14791 			}
14792 
14793 			if (!w)
14794 				alert(t.editor.getLang('popup_blocked'));
14795 		},
14796 
14797 		close : function(w) {
14798 			w.close();
14799 			this.onClose.dispatch(this);
14800 		},
14801 
14802 		createInstance : function(cl, a, b, c, d, e) {
14803 			var f = tinymce.resolve(cl);
14804 
14805 			return new f(a, b, c, d, e);
14806 		},
14807 
14808 		confirm : function(t, cb, s, w) {
14809 			w = w || window;
14810 
14811 			cb.call(s || this, w.confirm(this._decode(this.editor.getLang(t, t))));
14812 		},
14813 
14814 		alert : function(tx, cb, s, w) {
14815 			var t = this;
14816 
14817 			w = w || window;
14818 			w.alert(t._decode(t.editor.getLang(tx, tx)));
14819 
14820 			if (cb)
14821 				cb.call(s || t);
14822 		},
14823 
14824 		resizeBy : function(dw, dh, win) {
14825 			win.resizeBy(dw, dh);
14826 		},
14827 
14828 		// Internal functions
14829 
14830 		_decode : function(s) {
14831 			return tinymce.DOM.decode(s).replace(/\\n/g, '\n');
14832 		}
14833 	});
14834 }(tinymce));
14835 (function(tinymce) {
14836 	tinymce.Formatter = function(ed) {
14837 		var formats = {},
14838 			each = tinymce.each,
14839 			dom = ed.dom,
14840 			selection = ed.selection,
14841 			TreeWalker = tinymce.dom.TreeWalker,
14842 			rangeUtils = new tinymce.dom.RangeUtils(dom),
14843 			isValid = ed.schema.isValidChild,
14844 			isBlock = dom.isBlock,
14845 			forcedRootBlock = ed.settings.forced_root_block,
14846 			nodeIndex = dom.nodeIndex,
14847 			INVISIBLE_CHAR = tinymce.isGecko ? '\u200B' : '\uFEFF',
14848 			MCE_ATTR_RE = /^(src|href|style)$/,
14849 			FALSE = false,
14850 			TRUE = true,
14851 			formatChangeData,
14852 			undef,
14853 			getContentEditable = dom.getContentEditable;
14854 
14855 		function isArray(obj) {
14856 			return obj instanceof Array;
14857 		};
14858 
14859 		function getParents(node, selector) {
14860 			return dom.getParents(node, selector, dom.getRoot());
14861 		};
14862 
14863 		function isCaretNode(node) {
14864 			return node.nodeType === 1 && node.id === '_mce_caret';
14865 		};
14866 
14867 		function defaultFormats() {
14868 			register({
14869 				alignleft : [
14870 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}, defaultBlock: 'div'},
14871 					{selector : 'img,table', collapsed : false, styles : {'float' : 'left'}}
14872 				],
14873 
14874 				aligncenter : [
14875 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}, defaultBlock: 'div'},
14876 					{selector : 'img', collapsed : false, styles : {display : 'block', marginLeft : 'auto', marginRight : 'auto'}},
14877 					{selector : 'table', collapsed : false, styles : {marginLeft : 'auto', marginRight : 'auto'}}
14878 				],
14879 
14880 				alignright : [
14881 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}, defaultBlock: 'div'},
14882 					{selector : 'img,table', collapsed : false, styles : {'float' : 'right'}}
14883 				],
14884 
14885 				alignfull : [
14886 					{selector : 'figure,p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'justify'}, defaultBlock: 'div'}
14887 				],
14888 
14889 				bold : [
14890 					{inline : 'strong', remove : 'all'},
14891 					{inline : 'span', styles : {fontWeight : 'bold'}},
14892 					{inline : 'b', remove : 'all'}
14893 				],
14894 
14895 				italic : [
14896 					{inline : 'em', remove : 'all'},
14897 					{inline : 'span', styles : {fontStyle : 'italic'}},
14898 					{inline : 'i', remove : 'all'}
14899 				],
14900 
14901 				underline : [
14902 					{inline : 'span', styles : {textDecoration : 'underline'}, exact : true},
14903 					{inline : 'u', remove : 'all'}
14904 				],
14905 
14906 				strikethrough : [
14907 					{inline : 'span', styles : {textDecoration : 'line-through'}, exact : true},
14908 					{inline : 'strike', remove : 'all'}
14909 				],
14910 
14911 				forecolor : {inline : 'span', styles : {color : '%value'}, wrap_links : false},
14912 				hilitecolor : {inline : 'span', styles : {backgroundColor : '%value'}, wrap_links : false},
14913 				fontname : {inline : 'span', styles : {fontFamily : '%value'}},
14914 				fontsize : {inline : 'span', styles : {fontSize : '%value'}},
14915 				fontsize_class : {inline : 'span', attributes : {'class' : '%value'}},
14916 				blockquote : {block : 'blockquote', wrapper : 1, remove : 'all'},
14917 				subscript : {inline : 'sub'},
14918 				superscript : {inline : 'sup'},
14919 
14920 				link : {inline : 'a', selector : 'a', remove : 'all', split : true, deep : true,
14921 					onmatch : function(node) {
14922 						return true;
14923 					},
14924 
14925 					onformat : function(elm, fmt, vars) {
14926 						each(vars, function(value, key) {
14927 							dom.setAttrib(elm, key, value);
14928 						});
14929 					}
14930 				},
14931 
14932 				removeformat : [
14933 					{selector : 'b,strong,em,i,font,u,strike', remove : 'all', split : true, expand : false, block_expand : true, deep : true},
14934 					{selector : 'span', attributes : ['style', 'class'], remove : 'empty', split : true, expand : false, deep : true},
14935 					{selector : '*', attributes : ['style', 'class'], split : false, expand : false, deep : true}
14936 				]
14937 			});
14938 
14939 			// Register default block formats
14940 			each('p h1 h2 h3 h4 h5 h6 div address pre div code dt dd samp'.split(/\s/), function(name) {
14941 				register(name, {block : name, remove : 'all'});
14942 			});
14943 
14944 			// Register user defined formats
14945 			register(ed.settings.formats);
14946 		};
14947 
14948 		function addKeyboardShortcuts() {
14949 			// Add some inline shortcuts
14950 			ed.addShortcut('ctrl+b', 'bold_desc', 'Bold');
14951 			ed.addShortcut('ctrl+i', 'italic_desc', 'Italic');
14952 			ed.addShortcut('ctrl+u', 'underline_desc', 'Underline');
14953 
14954 			// BlockFormat shortcuts keys
14955 			for (var i = 1; i <= 6; i++) {
14956 				ed.addShortcut('ctrl+' + i, '', ['FormatBlock', false, 'h' + i]);
14957 			}
14958 
14959 			ed.addShortcut('ctrl+7', '', ['FormatBlock', false, 'p']);
14960 			ed.addShortcut('ctrl+8', '', ['FormatBlock', false, 'div']);
14961 			ed.addShortcut('ctrl+9', '', ['FormatBlock', false, 'address']);
14962 		};
14963 
14964 		// Public functions
14965 
14966 		function get(name) {
14967 			return name ? formats[name] : formats;
14968 		};
14969 
14970 		function register(name, format) {
14971 			if (name) {
14972 				if (typeof(name) !== 'string') {
14973 					each(name, function(format, name) {
14974 						register(name, format);
14975 					});
14976 				} else {
14977 					// Force format into array and add it to internal collection
14978 					format = format.length ? format : [format];
14979 
14980 					each(format, function(format) {
14981 						// Set deep to false by default on selector formats this to avoid removing
14982 						// alignment on images inside paragraphs when alignment is changed on paragraphs
14983 						if (format.deep === undef)
14984 							format.deep = !format.selector;
14985 
14986 						// Default to true
14987 						if (format.split === undef)
14988 							format.split = !format.selector || format.inline;
14989 
14990 						// Default to true
14991 						if (format.remove === undef && format.selector && !format.inline)
14992 							format.remove = 'none';
14993 
14994 						// Mark format as a mixed format inline + block level
14995 						if (format.selector && format.inline) {
14996 							format.mixed = true;
14997 							format.block_expand = true;
14998 						}
14999 
15000 						// Split classes if needed
15001 						if (typeof(format.classes) === 'string')
15002 							format.classes = format.classes.split(/\s+/);
15003 					});
15004 
15005 					formats[name] = format;
15006 				}
15007 			}
15008 		};
15009 
15010 		var getTextDecoration = function(node) {
15011 			var decoration;
15012 
15013 			ed.dom.getParent(node, function(n) {
15014 				decoration = ed.dom.getStyle(n, 'text-decoration');
15015 				return decoration && decoration !== 'none';
15016 			});
15017 
15018 			return decoration;
15019 		};
15020 
15021 		var processUnderlineAndColor = function(node) {
15022 			var textDecoration;
15023 			if (node.nodeType === 1 && node.parentNode && node.parentNode.nodeType === 1) {
15024 				textDecoration = getTextDecoration(node.parentNode);
15025 				if (ed.dom.getStyle(node, 'color') && textDecoration) {
15026 					ed.dom.setStyle(node, 'text-decoration', textDecoration);
15027 				} else if (ed.dom.getStyle(node, 'textdecoration') === textDecoration) {
15028 					ed.dom.setStyle(node, 'text-decoration', null);
15029 				}
15030 			}
15031 		};
15032 
15033 		function apply(name, vars, node) {
15034 			var formatList = get(name), format = formatList[0], bookmark, rng, i, isCollapsed = selection.isCollapsed();
15035 
15036 			function setElementFormat(elm, fmt) {
15037 				fmt = fmt || format;
15038 
15039 				if (elm) {
15040 					if (fmt.onformat) {
15041 						fmt.onformat(elm, fmt, vars, node);
15042 					}
15043 
15044 					each(fmt.styles, function(value, name) {
15045 						dom.setStyle(elm, name, replaceVars(value, vars));
15046 					});
15047 
15048 					each(fmt.attributes, function(value, name) {
15049 						dom.setAttrib(elm, name, replaceVars(value, vars));
15050 					});
15051 
15052 					each(fmt.classes, function(value) {
15053 						value = replaceVars(value, vars);
15054 
15055 						if (!dom.hasClass(elm, value))
15056 							dom.addClass(elm, value);
15057 					});
15058 				}
15059 			};
15060 			function adjustSelectionToVisibleSelection() {
15061 				function findSelectionEnd(start, end) {
15062 					var walker = new TreeWalker(end);
15063 					for (node = walker.current(); node; node = walker.prev()) {
15064 						if (node.childNodes.length > 1 || node == start || node.tagName == 'BR') {
15065 							return node;
15066 						}
15067 					}
15068 				};
15069 
15070 				// Adjust selection so that a end container with a end offset of zero is not included in the selection
15071 				// as this isn't visible to the user.
15072 				var rng = ed.selection.getRng();
15073 				var start = rng.startContainer;
15074 				var end = rng.endContainer;
15075 
15076 				if (start != end && rng.endOffset === 0) {
15077 					var newEnd = findSelectionEnd(start, end);
15078 					var endOffset = newEnd.nodeType == 3 ? newEnd.length : newEnd.childNodes.length;
15079 
15080 					rng.setEnd(newEnd, endOffset);
15081 				}
15082 
15083 				return rng;
15084 			}
15085 			
15086 			function applyStyleToList(node, bookmark, wrapElm, newWrappers, process){
15087 				var nodes = [], listIndex = -1, list, startIndex = -1, endIndex = -1, currentWrapElm;
15088 				
15089 				// find the index of the first child list.
15090 				each(node.childNodes, function(n, index) {
15091 					if (n.nodeName === "UL" || n.nodeName === "OL") {
15092 						listIndex = index;
15093 						list = n;
15094 						return false;
15095 					}
15096 				});
15097 				
15098 				// get the index of the bookmarks
15099 				each(node.childNodes, function(n, index) {
15100 					if (n.nodeName === "SPAN" && dom.getAttrib(n, "data-mce-type") == "bookmark") {
15101 						if (n.id == bookmark.id + "_start") {
15102 							startIndex = index;
15103 						} else if (n.id == bookmark.id + "_end") {
15104 							endIndex = index;
15105 						}
15106 					}
15107 				});
15108 				
15109 				// if the selection spans across an embedded list, or there isn't an embedded list - handle processing normally
15110 				if (listIndex <= 0 || (startIndex < listIndex && endIndex > listIndex)) {
15111 					each(tinymce.grep(node.childNodes), process);
15112 					return 0;
15113 				} else {
15114 					currentWrapElm = dom.clone(wrapElm, FALSE);
15115 
15116 					// create a list of the nodes on the same side of the list as the selection
15117 					each(tinymce.grep(node.childNodes), function(n, index) {
15118 						if ((startIndex < listIndex && index < listIndex) || (startIndex > listIndex && index > listIndex)) {
15119 							nodes.push(n); 
15120 							n.parentNode.removeChild(n);
15121 						}
15122 					});
15123 
15124 					// insert the wrapping element either before or after the list.
15125 					if (startIndex < listIndex) {
15126 						node.insertBefore(currentWrapElm, list);
15127 					} else if (startIndex > listIndex) {
15128 						node.insertBefore(currentWrapElm, list.nextSibling);
15129 					}
15130 					
15131 					// add the new nodes to the list.
15132 					newWrappers.push(currentWrapElm);
15133 
15134 					each(nodes, function(node) {
15135 						currentWrapElm.appendChild(node);
15136 					});
15137 
15138 					return currentWrapElm;
15139 				}
15140 			};
15141 
15142 			function applyRngStyle(rng, bookmark, node_specific) {
15143 				var newWrappers = [], wrapName, wrapElm, contentEditable = true;
15144 
15145 				// Setup wrapper element
15146 				wrapName = format.inline || format.block;
15147 				wrapElm = dom.create(wrapName);
15148 				setElementFormat(wrapElm);
15149 
15150 				rangeUtils.walk(rng, function(nodes) {
15151 					var currentWrapElm;
15152 
15153 					function process(node) {
15154 						var nodeName, parentName, found, hasContentEditableState, lastContentEditable;
15155 
15156 						lastContentEditable = contentEditable;
15157 						nodeName = node.nodeName.toLowerCase();
15158 						parentName = node.parentNode.nodeName.toLowerCase();
15159 
15160 						// Node has a contentEditable value
15161 						if (node.nodeType === 1 && getContentEditable(node)) {
15162 							lastContentEditable = contentEditable;
15163 							contentEditable = getContentEditable(node) === "true";
15164 							hasContentEditableState = true; // We don't want to wrap the container only it's children
15165 						}
15166 
15167 						// Stop wrapping on br elements
15168 						if (isEq(nodeName, 'br')) {
15169 							currentWrapElm = 0;
15170 
15171 							// Remove any br elements when we wrap things
15172 							if (format.block)
15173 								dom.remove(node);
15174 
15175 							return;
15176 						}
15177 
15178 						// If node is wrapper type
15179 						if (format.wrapper && matchNode(node, name, vars)) {
15180 							currentWrapElm = 0;
15181 							return;
15182 						}
15183 
15184 						// Can we rename the block
15185 						if (contentEditable && !hasContentEditableState && format.block && !format.wrapper && isTextBlock(nodeName)) {
15186 							node = dom.rename(node, wrapName);
15187 							setElementFormat(node);
15188 							newWrappers.push(node);
15189 							currentWrapElm = 0;
15190 							return;
15191 						}
15192 
15193 						// Handle selector patterns
15194 						if (format.selector) {
15195 							// Look for matching formats
15196 							each(formatList, function(format) {
15197 								// Check collapsed state if it exists
15198 								if ('collapsed' in format && format.collapsed !== isCollapsed) {
15199 									return;
15200 								}
15201 
15202 								if (dom.is(node, format.selector) && !isCaretNode(node)) {
15203 									setElementFormat(node, format);
15204 									found = true;
15205 								}
15206 							});
15207 
15208 							// Continue processing if a selector match wasn't found and a inline element is defined
15209 							if (!format.inline || found) {
15210 								currentWrapElm = 0;
15211 								return;
15212 							}
15213 						}
15214 
15215 						// Is it valid to wrap this item
15216 						if (contentEditable && !hasContentEditableState && isValid(wrapName, nodeName) && isValid(parentName, wrapName) &&
15217 								!(!node_specific && node.nodeType === 3 && node.nodeValue.length === 1 && node.nodeValue.charCodeAt(0) === 65279) && !isCaretNode(node)) {
15218 							// Start wrapping
15219 							if (!currentWrapElm) {
15220 								// Wrap the node
15221 								currentWrapElm = dom.clone(wrapElm, FALSE);
15222 								node.parentNode.insertBefore(currentWrapElm, node);
15223 								newWrappers.push(currentWrapElm);
15224 							}
15225 
15226 							currentWrapElm.appendChild(node);
15227 						} else if (nodeName == 'li' && bookmark) {
15228 							// Start wrapping - if we are in a list node and have a bookmark, then we will always begin by wrapping in a new element.
15229 							currentWrapElm = applyStyleToList(node, bookmark, wrapElm, newWrappers, process);
15230 						} else {
15231 							// Start a new wrapper for possible children
15232 							currentWrapElm = 0;
15233 							
15234 							each(tinymce.grep(node.childNodes), process);
15235 
15236 							if (hasContentEditableState) {
15237 								contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15238 							}
15239 
15240 							// End the last wrapper
15241 							currentWrapElm = 0;
15242 						}
15243 					};
15244 
15245 					// Process siblings from range
15246 					each(nodes, process);
15247 				});
15248 
15249 				// Wrap links inside as well, for example color inside a link when the wrapper is around the link
15250 				if (format.wrap_links === false) {
15251 					each(newWrappers, function(node) {
15252 						function process(node) {
15253 							var i, currentWrapElm, children;
15254 
15255 							if (node.nodeName === 'A') {
15256 								currentWrapElm = dom.clone(wrapElm, FALSE);
15257 								newWrappers.push(currentWrapElm);
15258 
15259 								children = tinymce.grep(node.childNodes);
15260 								for (i = 0; i < children.length; i++)
15261 									currentWrapElm.appendChild(children[i]);
15262 
15263 								node.appendChild(currentWrapElm);
15264 							}
15265 
15266 							each(tinymce.grep(node.childNodes), process);
15267 						};
15268 
15269 						process(node);
15270 					});
15271 				}
15272 
15273 				// Cleanup
15274 				
15275 				each(newWrappers, function(node) {
15276 					var childCount;
15277 
15278 					function getChildCount(node) {
15279 						var count = 0;
15280 
15281 						each(node.childNodes, function(node) {
15282 							if (!isWhiteSpaceNode(node) && !isBookmarkNode(node))
15283 								count++;
15284 						});
15285 
15286 						return count;
15287 					};
15288 
15289 					function mergeStyles(node) {
15290 						var child, clone;
15291 
15292 						each(node.childNodes, function(node) {
15293 							if (node.nodeType == 1 && !isBookmarkNode(node) && !isCaretNode(node)) {
15294 								child = node;
15295 								return FALSE; // break loop
15296 							}
15297 						});
15298 
15299 						// If child was found and of the same type as the current node
15300 						if (child && matchName(child, format)) {
15301 							clone = dom.clone(child, FALSE);
15302 							setElementFormat(clone);
15303 
15304 							dom.replace(clone, node, TRUE);
15305 							dom.remove(child, 1);
15306 						}
15307 
15308 						return clone || node;
15309 					};
15310 
15311 					childCount = getChildCount(node);
15312 
15313 					// Remove empty nodes but only if there is multiple wrappers and they are not block
15314 					// elements so never remove single <h1></h1> since that would remove the currrent empty block element where the caret is at
15315 					if ((newWrappers.length > 1 || !isBlock(node)) && childCount === 0) {
15316 						dom.remove(node, 1);
15317 						return;
15318 					}
15319 
15320 					if (format.inline || format.wrapper) {
15321 						// Merges the current node with it's children of similar type to reduce the number of elements
15322 						if (!format.exact && childCount === 1)
15323 							node = mergeStyles(node);
15324 
15325 						// Remove/merge children
15326 						each(formatList, function(format) {
15327 							// Merge all children of similar type will move styles from child to parent
15328 							// this: <span style="color:red"><b><span style="color:red; font-size:10px">text</span></b></span>
15329 							// will become: <span style="color:red"><b><span style="font-size:10px">text</span></b></span>
15330 							each(dom.select(format.inline, node), function(child) {
15331 								var parent;
15332 
15333 								// When wrap_links is set to false we don't want
15334 								// to remove the format on children within links
15335 								if (format.wrap_links === false) {
15336 									parent = child.parentNode;
15337 
15338 									do {
15339 										if (parent.nodeName === 'A')
15340 											return;
15341 									} while (parent = parent.parentNode);
15342 								}
15343 
15344 								removeFormat(format, vars, child, format.exact ? child : null);
15345 							});
15346 						});
15347 
15348 						// Remove child if direct parent is of same type
15349 						if (matchNode(node.parentNode, name, vars)) {
15350 							dom.remove(node, 1);
15351 							node = 0;
15352 							return TRUE;
15353 						}
15354 
15355 						// Look for parent with similar style format
15356 						if (format.merge_with_parents) {
15357 							dom.getParent(node.parentNode, function(parent) {
15358 								if (matchNode(parent, name, vars)) {
15359 									dom.remove(node, 1);
15360 									node = 0;
15361 									return TRUE;
15362 								}
15363 							});
15364 						}
15365 
15366 						// Merge next and previous siblings if they are similar <b>text</b><b>text</b> becomes <b>texttext</b>
15367 						if (node && format.merge_siblings !== false) {
15368 							node = mergeSiblings(getNonWhiteSpaceSibling(node), node);
15369 							node = mergeSiblings(node, getNonWhiteSpaceSibling(node, TRUE));
15370 						}
15371 					}
15372 				});
15373 			};
15374 
15375 			if (format) {
15376 				if (node) {
15377 					if (node.nodeType) {
15378 						rng = dom.createRng();
15379 						rng.setStartBefore(node);
15380 						rng.setEndAfter(node);
15381 						applyRngStyle(expandRng(rng, formatList), null, true);
15382 					} else {
15383 						applyRngStyle(node, null, true);
15384 					}
15385 				} else {
15386 					if (!isCollapsed || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15387 						// Obtain selection node before selection is unselected by applyRngStyle()
15388 						var curSelNode = ed.selection.getNode();
15389 
15390 						// If the formats have a default block and we can't find a parent block then start wrapping it with a DIV this is for forced_root_blocks: false
15391 						// It's kind of a hack but people should be using the default block type P since all desktop editors work that way
15392 						if (!forcedRootBlock && formatList[0].defaultBlock && !dom.getParent(curSelNode, dom.isBlock)) {
15393 							apply(formatList[0].defaultBlock);
15394 						}
15395 
15396 						// Apply formatting to selection
15397 						ed.selection.setRng(adjustSelectionToVisibleSelection());
15398 						bookmark = selection.getBookmark();
15399 						applyRngStyle(expandRng(selection.getRng(TRUE), formatList), bookmark);
15400 
15401 						// Colored nodes should be underlined so that the color of the underline matches the text color.
15402 						if (format.styles && (format.styles.color || format.styles.textDecoration)) {
15403 							tinymce.walk(curSelNode, processUnderlineAndColor, 'childNodes');
15404 							processUnderlineAndColor(curSelNode);
15405 						}
15406 
15407 						selection.moveToBookmark(bookmark);
15408 						moveStart(selection.getRng(TRUE));
15409 						ed.nodeChanged();
15410 					} else
15411 						performCaretAction('apply', name, vars);
15412 				}
15413 			}
15414 		};
15415 
15416 		function remove(name, vars, node) {
15417 			var formatList = get(name), format = formatList[0], bookmark, i, rng, contentEditable = true;
15418 
15419 			// Merges the styles for each node
15420 			function process(node) {
15421 				var children, i, l, localContentEditable, lastContentEditable, hasContentEditableState;
15422 
15423 				// Node has a contentEditable value
15424 				if (node.nodeType === 1 && getContentEditable(node)) {
15425 					lastContentEditable = contentEditable;
15426 					contentEditable = getContentEditable(node) === "true";
15427 					hasContentEditableState = true; // We don't want to wrap the container only it's children
15428 				}
15429 
15430 				// Grab the children first since the nodelist might be changed
15431 				children = tinymce.grep(node.childNodes);
15432 
15433 				// Process current node
15434 				if (contentEditable && !hasContentEditableState) {
15435 					for (i = 0, l = formatList.length; i < l; i++) {
15436 						if (removeFormat(formatList[i], vars, node, node))
15437 							break;
15438 					}
15439 				}
15440 
15441 				// Process the children
15442 				if (format.deep) {
15443 					if (children.length) {					
15444 						for (i = 0, l = children.length; i < l; i++)
15445 							process(children[i]);
15446 
15447 						if (hasContentEditableState) {
15448 							contentEditable = lastContentEditable; // Restore last contentEditable state from stack
15449 						}
15450 					}
15451 				}
15452 			};
15453 
15454 			function findFormatRoot(container) {
15455 				var formatRoot;
15456 
15457 				// Find format root
15458 				each(getParents(container.parentNode).reverse(), function(parent) {
15459 					var format;
15460 
15461 					// Find format root element
15462 					if (!formatRoot && parent.id != '_start' && parent.id != '_end') {
15463 						// Is the node matching the format we are looking for
15464 						format = matchNode(parent, name, vars);
15465 						if (format && format.split !== false)
15466 							formatRoot = parent;
15467 					}
15468 				});
15469 
15470 				return formatRoot;
15471 			};
15472 
15473 			function wrapAndSplit(format_root, container, target, split) {
15474 				var parent, clone, lastClone, firstClone, i, formatRootParent;
15475 
15476 				// Format root found then clone formats and split it
15477 				if (format_root) {
15478 					formatRootParent = format_root.parentNode;
15479 
15480 					for (parent = container.parentNode; parent && parent != formatRootParent; parent = parent.parentNode) {
15481 						clone = dom.clone(parent, FALSE);
15482 
15483 						for (i = 0; i < formatList.length; i++) {
15484 							if (removeFormat(formatList[i], vars, clone, clone)) {
15485 								clone = 0;
15486 								break;
15487 							}
15488 						}
15489 
15490 						// Build wrapper node
15491 						if (clone) {
15492 							if (lastClone)
15493 								clone.appendChild(lastClone);
15494 
15495 							if (!firstClone)
15496 								firstClone = clone;
15497 
15498 							lastClone = clone;
15499 						}
15500 					}
15501 
15502 					// Never split block elements if the format is mixed
15503 					if (split && (!format.mixed || !isBlock(format_root)))
15504 						container = dom.split(format_root, container);
15505 
15506 					// Wrap container in cloned formats
15507 					if (lastClone) {
15508 						target.parentNode.insertBefore(lastClone, target);
15509 						firstClone.appendChild(target);
15510 					}
15511 				}
15512 
15513 				return container;
15514 			};
15515 
15516 			function splitToFormatRoot(container) {
15517 				return wrapAndSplit(findFormatRoot(container), container, container, true);
15518 			};
15519 
15520 			function unwrap(start) {
15521 				var node = dom.get(start ? '_start' : '_end'),
15522 					out = node[start ? 'firstChild' : 'lastChild'];
15523 
15524 				// If the end is placed within the start the result will be removed
15525 				// So this checks if the out node is a bookmark node if it is it
15526 				// checks for another more suitable node
15527 				if (isBookmarkNode(out))
15528 					out = out[start ? 'firstChild' : 'lastChild'];
15529 
15530 				dom.remove(node, true);
15531 
15532 				return out;
15533 			};
15534 
15535 			function removeRngStyle(rng) {
15536 				var startContainer, endContainer, node;
15537 
15538 				rng = expandRng(rng, formatList, TRUE);
15539 
15540 				if (format.split) {
15541 					startContainer = getContainer(rng, TRUE);
15542 					endContainer = getContainer(rng);
15543 
15544 					if (startContainer != endContainer) {
15545 						// WebKit will render the table incorrectly if we wrap a TD in a SPAN so lets see if the can use the first child instead
15546 						// This will happen if you tripple click a table cell and use remove formatting
15547 						if (/^(TR|TD)$/.test(startContainer.nodeName) && startContainer.firstChild) {
15548 							startContainer = (startContainer.nodeName == "TD" ? startContainer.firstChild : startContainer.firstChild.firstChild) || startContainer;
15549 						}
15550 
15551 						// Wrap start/end nodes in span element since these might be cloned/moved
15552 						startContainer = wrap(startContainer, 'span', {id : '_start', 'data-mce-type' : 'bookmark'});
15553 						endContainer = wrap(endContainer, 'span', {id : '_end', 'data-mce-type' : 'bookmark'});
15554 
15555 						// Split start/end
15556 						splitToFormatRoot(startContainer);
15557 						splitToFormatRoot(endContainer);
15558 
15559 						// Unwrap start/end to get real elements again
15560 						startContainer = unwrap(TRUE);
15561 						endContainer = unwrap();
15562 					} else
15563 						startContainer = endContainer = splitToFormatRoot(startContainer);
15564 
15565 					// Update range positions since they might have changed after the split operations
15566 					rng.startContainer = startContainer.parentNode;
15567 					rng.startOffset = nodeIndex(startContainer);
15568 					rng.endContainer = endContainer.parentNode;
15569 					rng.endOffset = nodeIndex(endContainer) + 1;
15570 				}
15571 
15572 				// Remove items between start/end
15573 				rangeUtils.walk(rng, function(nodes) {
15574 					each(nodes, function(node) {
15575 						process(node);
15576 
15577 						// Remove parent span if it only contains text-decoration: underline, yet a parent node is also underlined.
15578 						if (node.nodeType === 1 && ed.dom.getStyle(node, 'text-decoration') === 'underline' && node.parentNode && getTextDecoration(node.parentNode) === 'underline') {
15579 							removeFormat({'deep': false, 'exact': true, 'inline': 'span', 'styles': {'textDecoration' : 'underline'}}, null, node);
15580 						}
15581 					});
15582 				});
15583 			};
15584 
15585 			// Handle node
15586 			if (node) {
15587 				if (node.nodeType) {
15588 					rng = dom.createRng();
15589 					rng.setStartBefore(node);
15590 					rng.setEndAfter(node);
15591 					removeRngStyle(rng);
15592 				} else {
15593 					removeRngStyle(node);
15594 				}
15595 
15596 				return;
15597 			}
15598 
15599 			if (!selection.isCollapsed() || !format.inline || dom.select('td.mceSelected,th.mceSelected').length) {
15600 				bookmark = selection.getBookmark();
15601 				removeRngStyle(selection.getRng(TRUE));
15602 				selection.moveToBookmark(bookmark);
15603 
15604 				// Check if start element still has formatting then we are at: "<b>text|</b>text" and need to move the start into the next text node
15605 				if (format.inline && match(name, vars, selection.getStart())) {
15606 					moveStart(selection.getRng(true));
15607 				}
15608 
15609 				ed.nodeChanged();
15610 			} else
15611 				performCaretAction('remove', name, vars);
15612 		};
15613 
15614 		function toggle(name, vars, node) {
15615 			var fmt = get(name);
15616 
15617 			if (match(name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle))
15618 				remove(name, vars, node);
15619 			else
15620 				apply(name, vars, node);
15621 		};
15622 
15623 		function matchNode(node, name, vars, similar) {
15624 			var formatList = get(name), format, i, classes;
15625 
15626 			function matchItems(node, format, item_name) {
15627 				var key, value, items = format[item_name], i;
15628 
15629 				// Custom match
15630 				if (format.onmatch) {
15631 					return format.onmatch(node, format, item_name);
15632 				}
15633 
15634 				// Check all items
15635 				if (items) {
15636 					// Non indexed object
15637 					if (items.length === undef) {
15638 						for (key in items) {
15639 							if (items.hasOwnProperty(key)) {
15640 								if (item_name === 'attributes')
15641 									value = dom.getAttrib(node, key);
15642 								else
15643 									value = getStyle(node, key);
15644 
15645 								if (similar && !value && !format.exact)
15646 									return;
15647 
15648 								if ((!similar || format.exact) && !isEq(value, replaceVars(items[key], vars)))
15649 									return;
15650 							}
15651 						}
15652 					} else {
15653 						// Only one match needed for indexed arrays
15654 						for (i = 0; i < items.length; i++) {
15655 							if (item_name === 'attributes' ? dom.getAttrib(node, items[i]) : getStyle(node, items[i]))
15656 								return format;
15657 						}
15658 					}
15659 				}
15660 
15661 				return format;
15662 			};
15663 
15664 			if (formatList && node) {
15665 				// Check each format in list
15666 				for (i = 0; i < formatList.length; i++) {
15667 					format = formatList[i];
15668 
15669 					// Name name, attributes, styles and classes
15670 					if (matchName(node, format) && matchItems(node, format, 'attributes') && matchItems(node, format, 'styles')) {
15671 						// Match classes
15672 						if (classes = format.classes) {
15673 							for (i = 0; i < classes.length; i++) {
15674 								if (!dom.hasClass(node, classes[i]))
15675 									return;
15676 							}
15677 						}
15678 
15679 						return format;
15680 					}
15681 				}
15682 			}
15683 		};
15684 
15685 		function match(name, vars, node) {
15686 			var startNode;
15687 
15688 			function matchParents(node) {
15689 				// Find first node with similar format settings
15690 				node = dom.getParent(node, function(node) {
15691 					return !!matchNode(node, name, vars, true);
15692 				});
15693 
15694 				// Do an exact check on the similar format element
15695 				return matchNode(node, name, vars);
15696 			};
15697 
15698 			// Check specified node
15699 			if (node)
15700 				return matchParents(node);
15701 
15702 			// Check selected node
15703 			node = selection.getNode();
15704 			if (matchParents(node))
15705 				return TRUE;
15706 
15707 			// Check start node if it's different
15708 			startNode = selection.getStart();
15709 			if (startNode != node) {
15710 				if (matchParents(startNode))
15711 					return TRUE;
15712 			}
15713 
15714 			return FALSE;
15715 		};
15716 
15717 		function matchAll(names, vars) {
15718 			var startElement, matchedFormatNames = [], checkedMap = {}, i, ni, name;
15719 
15720 			// Check start of selection for formats
15721 			startElement = selection.getStart();
15722 			dom.getParent(startElement, function(node) {
15723 				var i, name;
15724 
15725 				for (i = 0; i < names.length; i++) {
15726 					name = names[i];
15727 
15728 					if (!checkedMap[name] && matchNode(node, name, vars)) {
15729 						checkedMap[name] = true;
15730 						matchedFormatNames.push(name);
15731 					}
15732 				}
15733 			}, dom.getRoot());
15734 
15735 			return matchedFormatNames;
15736 		};
15737 
15738 		function canApply(name) {
15739 			var formatList = get(name), startNode, parents, i, x, selector;
15740 
15741 			if (formatList) {
15742 				startNode = selection.getStart();
15743 				parents = getParents(startNode);
15744 
15745 				for (x = formatList.length - 1; x >= 0; x--) {
15746 					selector = formatList[x].selector;
15747 
15748 					// Format is not selector based, then always return TRUE
15749 					if (!selector)
15750 						return TRUE;
15751 
15752 					for (i = parents.length - 1; i >= 0; i--) {
15753 						if (dom.is(parents[i], selector))
15754 							return TRUE;
15755 					}
15756 				}
15757 			}
15758 
15759 			return FALSE;
15760 		};
15761 
15762 		function formatChanged(formats, callback) {
15763 			var currentFormats;
15764 
15765 			// Setup format node change logic
15766 			if (!formatChangeData) {
15767 				formatChangeData = {};
15768 				currentFormats = {};
15769 
15770 				ed.onNodeChange.addToTop(function(ed, cm, node) {
15771 					var parents = getParents(node), matchedFormats = {};
15772 
15773 					// Check for new formats
15774 					each(formatChangeData, function(callbacks, format) {
15775 						each(parents, function(node) {
15776 							if (matchNode(node, format, {}, true)) {
15777 								if (!currentFormats[format]) {
15778 									// Execute callbacks
15779 									each(callbacks, function(callback) {
15780 										callback(true, {node: node, format: format, parents: parents});
15781 									});
15782 
15783 									currentFormats[format] = callbacks;
15784 								}
15785 
15786 								matchedFormats[format] = callbacks;
15787 								return false;
15788 							}
15789 						});
15790 					});
15791 
15792 					// Check if current formats still match
15793 					each(currentFormats, function(callbacks, format) {
15794 						if (!matchedFormats[format]) {
15795 							delete currentFormats[format];
15796 
15797 							each(callbacks, function(callback) {
15798 								callback(false, {node: node, format: format, parents: parents});
15799 							});
15800 						}
15801 					});
15802 				});
15803 			}
15804 
15805 			// Add format listeners
15806 			each(formats.split(','), function(format) {
15807 				if (!formatChangeData[format]) {
15808 					formatChangeData[format] = [];
15809 				}
15810 
15811 				formatChangeData[format].push(callback);
15812 			});
15813 
15814 			return this;
15815 		};
15816 
15817 		// Expose to public
15818 		tinymce.extend(this, {
15819 			get : get,
15820 			register : register,
15821 			apply : apply,
15822 			remove : remove,
15823 			toggle : toggle,
15824 			match : match,
15825 			matchAll : matchAll,
15826 			matchNode : matchNode,
15827 			canApply : canApply,
15828 			formatChanged: formatChanged
15829 		});
15830 
15831 		// Initialize
15832 		defaultFormats();
15833 		addKeyboardShortcuts();
15834 
15835 		// Private functions
15836 
15837 		function matchName(node, format) {
15838 			// Check for inline match
15839 			if (isEq(node, format.inline))
15840 				return TRUE;
15841 
15842 			// Check for block match
15843 			if (isEq(node, format.block))
15844 				return TRUE;
15845 
15846 			// Check for selector match
15847 			if (format.selector)
15848 				return dom.is(node, format.selector);
15849 		};
15850 
15851 		function isEq(str1, str2) {
15852 			str1 = str1 || '';
15853 			str2 = str2 || '';
15854 
15855 			str1 = '' + (str1.nodeName || str1);
15856 			str2 = '' + (str2.nodeName || str2);
15857 
15858 			return str1.toLowerCase() == str2.toLowerCase();
15859 		};
15860 
15861 		function getStyle(node, name) {
15862 			var styleVal = dom.getStyle(node, name);
15863 
15864 			// Force the format to hex
15865 			if (name == 'color' || name == 'backgroundColor')
15866 				styleVal = dom.toHex(styleVal);
15867 
15868 			// Opera will return bold as 700
15869 			if (name == 'fontWeight' && styleVal == 700)
15870 				styleVal = 'bold';
15871 
15872 			return '' + styleVal;
15873 		};
15874 
15875 		function replaceVars(value, vars) {
15876 			if (typeof(value) != "string")
15877 				value = value(vars);
15878 			else if (vars) {
15879 				value = value.replace(/%(\w+)/g, function(str, name) {
15880 					return vars[name] || str;
15881 				});
15882 			}
15883 
15884 			return value;
15885 		};
15886 
15887 		function isWhiteSpaceNode(node) {
15888 			return node && node.nodeType === 3 && /^([\t \r\n]+|)$/.test(node.nodeValue);
15889 		};
15890 
15891 		function wrap(node, name, attrs) {
15892 			var wrapper = dom.create(name, attrs);
15893 
15894 			node.parentNode.insertBefore(wrapper, node);
15895 			wrapper.appendChild(node);
15896 
15897 			return wrapper;
15898 		};
15899 
15900 		function expandRng(rng, format, remove) {
15901 			var sibling, lastIdx, leaf, endPoint,
15902 				startContainer = rng.startContainer,
15903 				startOffset = rng.startOffset,
15904 				endContainer = rng.endContainer,
15905 				endOffset = rng.endOffset;
15906 
15907 			// This function walks up the tree if there is no siblings before/after the node
15908 			function findParentContainer(start) {
15909 				var container, parent, child, sibling, siblingName, root;
15910 
15911 				container = parent = start ? startContainer : endContainer;
15912 				siblingName = start ? 'previousSibling' : 'nextSibling';
15913 				root = dom.getRoot();
15914 
15915 				// If it's a text node and the offset is inside the text
15916 				if (container.nodeType == 3 && !isWhiteSpaceNode(container)) {
15917 					if (start ? startOffset > 0 : endOffset < container.nodeValue.length) {
15918 						return container;
15919 					}
15920 				}
15921 
15922 				for (;;) {
15923 					// Stop expanding on block elements
15924 					if (!format[0].block_expand && isBlock(parent))
15925 						return parent;
15926 
15927 					// Walk left/right
15928 					for (sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
15929 						if (!isBookmarkNode(sibling) && !isWhiteSpaceNode(sibling)) {
15930 							return parent;
15931 						}
15932 					}
15933 
15934 					// Check if we can move up are we at root level or body level
15935 					if (parent.parentNode == root) {
15936 						container = parent;
15937 						break;
15938 					}
15939 
15940 					parent = parent.parentNode;
15941 				}
15942 
15943 				return container;
15944 			};
15945 
15946 			// This function walks down the tree to find the leaf at the selection.
15947 			// The offset is also returned as if node initially a leaf, the offset may be in the middle of the text node.
15948 			function findLeaf(node, offset) {
15949 				if (offset === undef)
15950 					offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15951 				while (node && node.hasChildNodes()) {
15952 					node = node.childNodes[offset];
15953 					if (node)
15954 						offset = node.nodeType === 3 ? node.length : node.childNodes.length;
15955 				}
15956 				return { node: node, offset: offset };
15957 			}
15958 
15959 			// If index based start position then resolve it
15960 			if (startContainer.nodeType == 1 && startContainer.hasChildNodes()) {
15961 				lastIdx = startContainer.childNodes.length - 1;
15962 				startContainer = startContainer.childNodes[startOffset > lastIdx ? lastIdx : startOffset];
15963 
15964 				if (startContainer.nodeType == 3)
15965 					startOffset = 0;
15966 			}
15967 
15968 			// If index based end position then resolve it
15969 			if (endContainer.nodeType == 1 && endContainer.hasChildNodes()) {
15970 				lastIdx = endContainer.childNodes.length - 1;
15971 				endContainer = endContainer.childNodes[endOffset > lastIdx ? lastIdx : endOffset - 1];
15972 
15973 				if (endContainer.nodeType == 3)
15974 					endOffset = endContainer.nodeValue.length;
15975 			}
15976 
15977 			// Expands the node to the closes contentEditable false element if it exists
15978 			function findParentContentEditable(node) {
15979 				var parent = node;
15980 
15981 				while (parent) {
15982 					if (parent.nodeType === 1 && getContentEditable(parent)) {
15983 						return getContentEditable(parent) === "false" ? parent : node;
15984 					}
15985 
15986 					parent = parent.parentNode;
15987 				}
15988 
15989 				return node;
15990 			};
15991 
15992 			function findWordEndPoint(container, offset, start) {
15993 				var walker, node, pos, lastTextNode;
15994 
15995 				function findSpace(node, offset) {
15996 					var pos, pos2, str = node.nodeValue;
15997 
15998 					if (typeof(offset) == "undefined") {
15999 						offset = start ? str.length : 0;
16000 					}
16001 
16002 					if (start) {
16003 						pos = str.lastIndexOf(' ', offset);
16004 						pos2 = str.lastIndexOf('\u00a0', offset);
16005 						pos = pos > pos2 ? pos : pos2;
16006 
16007 						// Include the space on remove to avoid tag soup
16008 						if (pos !== -1 && !remove) {
16009 							pos++;
16010 						}
16011 					} else {
16012 						pos = str.indexOf(' ', offset);
16013 						pos2 = str.indexOf('\u00a0', offset);
16014 						pos = pos !== -1 && (pos2 === -1 || pos < pos2) ? pos : pos2;
16015 					}
16016 
16017 					return pos;
16018 				};
16019 
16020 				if (container.nodeType === 3) {
16021 					pos = findSpace(container, offset);
16022 
16023 					if (pos !== -1) {
16024 						return {container : container, offset : pos};
16025 					}
16026 
16027 					lastTextNode = container;
16028 				}
16029 
16030 				// Walk the nodes inside the block
16031 				walker = new TreeWalker(container, dom.getParent(container, isBlock) || ed.getBody());
16032 				while (node = walker[start ? 'prev' : 'next']()) {
16033 					if (node.nodeType === 3) {
16034 						lastTextNode = node;
16035 						pos = findSpace(node);
16036 
16037 						if (pos !== -1) {
16038 							return {container : node, offset : pos};
16039 						}
16040 					} else if (isBlock(node)) {
16041 						break;
16042 					}
16043 				}
16044 
16045 				if (lastTextNode) {
16046 					if (start) {
16047 						offset = 0;
16048 					} else {
16049 						offset = lastTextNode.length;
16050 					}
16051 
16052 					return {container: lastTextNode, offset: offset};
16053 				}
16054 			};
16055 
16056 			function findSelectorEndPoint(container, sibling_name) {
16057 				var parents, i, y, curFormat;
16058 
16059 				if (container.nodeType == 3 && container.nodeValue.length === 0 && container[sibling_name])
16060 					container = container[sibling_name];
16061 
16062 				parents = getParents(container);
16063 				for (i = 0; i < parents.length; i++) {
16064 					for (y = 0; y < format.length; y++) {
16065 						curFormat = format[y];
16066 
16067 						// If collapsed state is set then skip formats that doesn't match that
16068 						if ("collapsed" in curFormat && curFormat.collapsed !== rng.collapsed)
16069 							continue;
16070 
16071 						if (dom.is(parents[i], curFormat.selector))
16072 							return parents[i];
16073 					}
16074 				}
16075 
16076 				return container;
16077 			};
16078 
16079 			function findBlockEndPoint(container, sibling_name, sibling_name2) {
16080 				var node;
16081 
16082 				// Expand to block of similar type
16083 				if (!format[0].wrapper)
16084 					node = dom.getParent(container, format[0].block);
16085 
16086 				// Expand to first wrappable block element or any block element
16087 				if (!node)
16088 					node = dom.getParent(container.nodeType == 3 ? container.parentNode : container, isBlock);
16089 
16090 				// Exclude inner lists from wrapping
16091 				if (node && format[0].wrapper)
16092 					node = getParents(node, 'ul,ol').reverse()[0] || node;
16093 
16094 				// Didn't find a block element look for first/last wrappable element
16095 				if (!node) {
16096 					node = container;
16097 
16098 					while (node[sibling_name] && !isBlock(node[sibling_name])) {
16099 						node = node[sibling_name];
16100 
16101 						// Break on BR but include it will be removed later on
16102 						// we can't remove it now since we need to check if it can be wrapped
16103 						if (isEq(node, 'br'))
16104 							break;
16105 					}
16106 				}
16107 
16108 				return node || container;
16109 			};
16110 
16111 			// Expand to closest contentEditable element
16112 			startContainer = findParentContentEditable(startContainer);
16113 			endContainer = findParentContentEditable(endContainer);
16114 
16115 			// Exclude bookmark nodes if possible
16116 			if (isBookmarkNode(startContainer.parentNode) || isBookmarkNode(startContainer)) {
16117 				startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
16118 				startContainer = startContainer.nextSibling || startContainer;
16119 
16120 				if (startContainer.nodeType == 3)
16121 					startOffset = 0;
16122 			}
16123 
16124 			if (isBookmarkNode(endContainer.parentNode) || isBookmarkNode(endContainer)) {
16125 				endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
16126 				endContainer = endContainer.previousSibling || endContainer;
16127 
16128 				if (endContainer.nodeType == 3)
16129 					endOffset = endContainer.length;
16130 			}
16131 
16132 			if (format[0].inline) {
16133 				if (rng.collapsed) {
16134 					// Expand left to closest word boundery
16135 					endPoint = findWordEndPoint(startContainer, startOffset, true);
16136 					if (endPoint) {
16137 						startContainer = endPoint.container;
16138 						startOffset = endPoint.offset;
16139 					}
16140 
16141 					// Expand right to closest word boundery
16142 					endPoint = findWordEndPoint(endContainer, endOffset);
16143 					if (endPoint) {
16144 						endContainer = endPoint.container;
16145 						endOffset = endPoint.offset;
16146 					}
16147 				}
16148 
16149 				// Avoid applying formatting to a trailing space.
16150 				leaf = findLeaf(endContainer, endOffset);
16151 				if (leaf.node) {
16152 					while (leaf.node && leaf.offset === 0 && leaf.node.previousSibling)
16153 						leaf = findLeaf(leaf.node.previousSibling);
16154 
16155 					if (leaf.node && leaf.offset > 0 && leaf.node.nodeType === 3 &&
16156 							leaf.node.nodeValue.charAt(leaf.offset - 1) === ' ') {
16157 
16158 						if (leaf.offset > 1) {
16159 							endContainer = leaf.node;
16160 							endContainer.splitText(leaf.offset - 1);
16161 						}
16162 					}
16163 				}
16164 			}
16165 
16166 			// Move start/end point up the tree if the leaves are sharp and if we are in different containers
16167 			// Example * becomes !: !<p><b><i>*text</i><i>text*</i></b></p>!
16168 			// This will reduce the number of wrapper elements that needs to be created
16169 			// Move start point up the tree
16170 			if (format[0].inline || format[0].block_expand) {
16171 				if (!format[0].inline || (startContainer.nodeType != 3 || startOffset === 0)) {
16172 					startContainer = findParentContainer(true);
16173 				}
16174 
16175 				if (!format[0].inline || (endContainer.nodeType != 3 || endOffset === endContainer.nodeValue.length)) {
16176 					endContainer = findParentContainer();
16177 				}
16178 			}
16179 
16180 			// Expand start/end container to matching selector
16181 			if (format[0].selector && format[0].expand !== FALSE && !format[0].inline) {
16182 				// Find new startContainer/endContainer if there is better one
16183 				startContainer = findSelectorEndPoint(startContainer, 'previousSibling');
16184 				endContainer = findSelectorEndPoint(endContainer, 'nextSibling');
16185 			}
16186 
16187 			// Expand start/end container to matching block element or text node
16188 			if (format[0].block || format[0].selector) {
16189 				// Find new startContainer/endContainer if there is better one
16190 				startContainer = findBlockEndPoint(startContainer, 'previousSibling');
16191 				endContainer = findBlockEndPoint(endContainer, 'nextSibling');
16192 
16193 				// Non block element then try to expand up the leaf
16194 				if (format[0].block) {
16195 					if (!isBlock(startContainer))
16196 						startContainer = findParentContainer(true);
16197 
16198 					if (!isBlock(endContainer))
16199 						endContainer = findParentContainer();
16200 				}
16201 			}
16202 
16203 			// Setup index for startContainer
16204 			if (startContainer.nodeType == 1) {
16205 				startOffset = nodeIndex(startContainer);
16206 				startContainer = startContainer.parentNode;
16207 			}
16208 
16209 			// Setup index for endContainer
16210 			if (endContainer.nodeType == 1) {
16211 				endOffset = nodeIndex(endContainer) + 1;
16212 				endContainer = endContainer.parentNode;
16213 			}
16214 
16215 			// Return new range like object
16216 			return {
16217 				startContainer : startContainer,
16218 				startOffset : startOffset,
16219 				endContainer : endContainer,
16220 				endOffset : endOffset
16221 			};
16222 		}
16223 
16224 		function removeFormat(format, vars, node, compare_node) {
16225 			var i, attrs, stylesModified;
16226 
16227 			// Check if node matches format
16228 			if (!matchName(node, format))
16229 				return FALSE;
16230 
16231 			// Should we compare with format attribs and styles
16232 			if (format.remove != 'all') {
16233 				// Remove styles
16234 				each(format.styles, function(value, name) {
16235 					value = replaceVars(value, vars);
16236 
16237 					// Indexed array
16238 					if (typeof(name) === 'number') {
16239 						name = value;
16240 						compare_node = 0;
16241 					}
16242 
16243 					if (!compare_node || isEq(getStyle(compare_node, name), value))
16244 						dom.setStyle(node, name, '');
16245 
16246 					stylesModified = 1;
16247 				});
16248 
16249 				// Remove style attribute if it's empty
16250 				if (stylesModified && dom.getAttrib(node, 'style') == '') {
16251 					node.removeAttribute('style');
16252 					node.removeAttribute('data-mce-style');
16253 				}
16254 
16255 				// Remove attributes
16256 				each(format.attributes, function(value, name) {
16257 					var valueOut;
16258 
16259 					value = replaceVars(value, vars);
16260 
16261 					// Indexed array
16262 					if (typeof(name) === 'number') {
16263 						name = value;
16264 						compare_node = 0;
16265 					}
16266 
16267 					if (!compare_node || isEq(dom.getAttrib(compare_node, name), value)) {
16268 						// Keep internal classes
16269 						if (name == 'class') {
16270 							value = dom.getAttrib(node, name);
16271 							if (value) {
16272 								// Build new class value where everything is removed except the internal prefixed classes
16273 								valueOut = '';
16274 								each(value.split(/\s+/), function(cls) {
16275 									if (/mce\w+/.test(cls))
16276 										valueOut += (valueOut ? ' ' : '') + cls;
16277 								});
16278 
16279 								// We got some internal classes left
16280 								if (valueOut) {
16281 									dom.setAttrib(node, name, valueOut);
16282 									return;
16283 								}
16284 							}
16285 						}
16286 
16287 						// IE6 has a bug where the attribute doesn't get removed correctly
16288 						if (name == "class")
16289 							node.removeAttribute('className');
16290 
16291 						// Remove mce prefixed attributes
16292 						if (MCE_ATTR_RE.test(name))
16293 							node.removeAttribute('data-mce-' + name);
16294 
16295 						node.removeAttribute(name);
16296 					}
16297 				});
16298 
16299 				// Remove classes
16300 				each(format.classes, function(value) {
16301 					value = replaceVars(value, vars);
16302 
16303 					if (!compare_node || dom.hasClass(compare_node, value))
16304 						dom.removeClass(node, value);
16305 				});
16306 
16307 				// Check for non internal attributes
16308 				attrs = dom.getAttribs(node);
16309 				for (i = 0; i < attrs.length; i++) {
16310 					if (attrs[i].nodeName.indexOf('_') !== 0)
16311 						return FALSE;
16312 				}
16313 			}
16314 
16315 			// Remove the inline child if it's empty for example <b> or <span>
16316 			if (format.remove != 'none') {
16317 				removeNode(node, format);
16318 				return TRUE;
16319 			}
16320 		};
16321 
16322 		function removeNode(node, format) {
16323 			var parentNode = node.parentNode, rootBlockElm;
16324 
16325 			function find(node, next, inc) {
16326 				node = getNonWhiteSpaceSibling(node, next, inc);
16327 
16328 				return !node || (node.nodeName == 'BR' || isBlock(node));
16329 			};
16330 
16331 			if (format.block) {
16332 				if (!forcedRootBlock) {
16333 					// Append BR elements if needed before we remove the block
16334 					if (isBlock(node) && !isBlock(parentNode)) {
16335 						if (!find(node, FALSE) && !find(node.firstChild, TRUE, 1))
16336 							node.insertBefore(dom.create('br'), node.firstChild);
16337 
16338 						if (!find(node, TRUE) && !find(node.lastChild, FALSE, 1))
16339 							node.appendChild(dom.create('br'));
16340 					}
16341 				} else {
16342 					// Wrap the block in a forcedRootBlock if we are at the root of document
16343 					if (parentNode == dom.getRoot()) {
16344 						if (!format.list_block || !isEq(node, format.list_block)) {
16345 							each(tinymce.grep(node.childNodes), function(node) {
16346 								if (isValid(forcedRootBlock, node.nodeName.toLowerCase())) {
16347 									if (!rootBlockElm)
16348 										rootBlockElm = wrap(node, forcedRootBlock);
16349 									else
16350 										rootBlockElm.appendChild(node);
16351 								} else
16352 									rootBlockElm = 0;
16353 							});
16354 						}
16355 					}
16356 				}
16357 			}
16358 
16359 			// Never remove nodes that isn't the specified inline element if a selector is specified too
16360 			if (format.selector && format.inline && !isEq(format.inline, node))
16361 				return;
16362 
16363 			dom.remove(node, 1);
16364 		};
16365 
16366 		function getNonWhiteSpaceSibling(node, next, inc) {
16367 			if (node) {
16368 				next = next ? 'nextSibling' : 'previousSibling';
16369 
16370 				for (node = inc ? node : node[next]; node; node = node[next]) {
16371 					if (node.nodeType == 1 || !isWhiteSpaceNode(node))
16372 						return node;
16373 				}
16374 			}
16375 		};
16376 
16377 		function isBookmarkNode(node) {
16378 			return node && node.nodeType == 1 && node.getAttribute('data-mce-type') == 'bookmark';
16379 		};
16380 
16381 		function mergeSiblings(prev, next) {
16382 			var marker, sibling, tmpSibling;
16383 
16384 			function compareElements(node1, node2) {
16385 				// Not the same name
16386 				if (node1.nodeName != node2.nodeName)
16387 					return FALSE;
16388 
16389 				function getAttribs(node) {
16390 					var attribs = {};
16391 
16392 					each(dom.getAttribs(node), function(attr) {
16393 						var name = attr.nodeName.toLowerCase();
16394 
16395 						// Don't compare internal attributes or style
16396 						if (name.indexOf('_') !== 0 && name !== 'style')
16397 							attribs[name] = dom.getAttrib(node, name);
16398 					});
16399 
16400 					return attribs;
16401 				};
16402 
16403 				function compareObjects(obj1, obj2) {
16404 					var value, name;
16405 
16406 					for (name in obj1) {
16407 						// Obj1 has item obj2 doesn't have
16408 						if (obj1.hasOwnProperty(name)) {
16409 							value = obj2[name];
16410 
16411 							// Obj2 doesn't have obj1 item
16412 							if (value === undef)
16413 								return FALSE;
16414 
16415 							// Obj2 item has a different value
16416 							if (obj1[name] != value)
16417 								return FALSE;
16418 
16419 							// Delete similar value
16420 							delete obj2[name];
16421 						}
16422 					}
16423 
16424 					// Check if obj 2 has something obj 1 doesn't have
16425 					for (name in obj2) {
16426 						// Obj2 has item obj1 doesn't have
16427 						if (obj2.hasOwnProperty(name))
16428 							return FALSE;
16429 					}
16430 
16431 					return TRUE;
16432 				};
16433 
16434 				// Attribs are not the same
16435 				if (!compareObjects(getAttribs(node1), getAttribs(node2)))
16436 					return FALSE;
16437 
16438 				// Styles are not the same
16439 				if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style'))))
16440 					return FALSE;
16441 
16442 				return TRUE;
16443 			};
16444 
16445 			function findElementSibling(node, sibling_name) {
16446 				for (sibling = node; sibling; sibling = sibling[sibling_name]) {
16447 					if (sibling.nodeType == 3 && sibling.nodeValue.length !== 0)
16448 						return node;
16449 
16450 					if (sibling.nodeType == 1 && !isBookmarkNode(sibling))
16451 						return sibling;
16452 				}
16453 
16454 				return node;
16455 			};
16456 
16457 			// Check if next/prev exists and that they are elements
16458 			if (prev && next) {
16459 				// If previous sibling is empty then jump over it
16460 				prev = findElementSibling(prev, 'previousSibling');
16461 				next = findElementSibling(next, 'nextSibling');
16462 
16463 				// Compare next and previous nodes
16464 				if (compareElements(prev, next)) {
16465 					// Append nodes between
16466 					for (sibling = prev.nextSibling; sibling && sibling != next;) {
16467 						tmpSibling = sibling;
16468 						sibling = sibling.nextSibling;
16469 						prev.appendChild(tmpSibling);
16470 					}
16471 
16472 					// Remove next node
16473 					dom.remove(next);
16474 
16475 					// Move children into prev node
16476 					each(tinymce.grep(next.childNodes), function(node) {
16477 						prev.appendChild(node);
16478 					});
16479 
16480 					return prev;
16481 				}
16482 			}
16483 
16484 			return next;
16485 		};
16486 
16487 		function isTextBlock(name) {
16488 			return /^(h[1-6]|p|div|pre|address|dl|dt|dd)$/.test(name);
16489 		};
16490 
16491 		function getContainer(rng, start) {
16492 			var container, offset, lastIdx, walker;
16493 
16494 			container = rng[start ? 'startContainer' : 'endContainer'];
16495 			offset = rng[start ? 'startOffset' : 'endOffset'];
16496 
16497 			if (container.nodeType == 1) {
16498 				lastIdx = container.childNodes.length - 1;
16499 
16500 				if (!start && offset)
16501 					offset--;
16502 
16503 				container = container.childNodes[offset > lastIdx ? lastIdx : offset];
16504 			}
16505 
16506 			// If start text node is excluded then walk to the next node
16507 			if (container.nodeType === 3 && start && offset >= container.nodeValue.length) {
16508 				container = new TreeWalker(container, ed.getBody()).next() || container;
16509 			}
16510 
16511 			// If end text node is excluded then walk to the previous node
16512 			if (container.nodeType === 3 && !start && offset === 0) {
16513 				container = new TreeWalker(container, ed.getBody()).prev() || container;
16514 			}
16515 
16516 			return container;
16517 		};
16518 
16519 		function performCaretAction(type, name, vars) {
16520 			var caretContainerId = '_mce_caret', debug = ed.settings.caret_debug;
16521 
16522 			// Creates a caret container bogus element
16523 			function createCaretContainer(fill) {
16524 				var caretContainer = dom.create('span', {id: caretContainerId, 'data-mce-bogus': true, style: debug ? 'color:red' : ''});
16525 
16526 				if (fill) {
16527 					caretContainer.appendChild(ed.getDoc().createTextNode(INVISIBLE_CHAR));
16528 				}
16529 
16530 				return caretContainer;
16531 			};
16532 
16533 			function isCaretContainerEmpty(node, nodes) {
16534 				while (node) {
16535 					if ((node.nodeType === 3 && node.nodeValue !== INVISIBLE_CHAR) || node.childNodes.length > 1) {
16536 						return false;
16537 					}
16538 
16539 					// Collect nodes
16540 					if (nodes && node.nodeType === 1) {
16541 						nodes.push(node);
16542 					}
16543 
16544 					node = node.firstChild;
16545 				}
16546 
16547 				return true;
16548 			};
16549 			
16550 			// Returns any parent caret container element
16551 			function getParentCaretContainer(node) {
16552 				while (node) {
16553 					if (node.id === caretContainerId) {
16554 						return node;
16555 					}
16556 
16557 					node = node.parentNode;
16558 				}
16559 			};
16560 
16561 			// Finds the first text node in the specified node
16562 			function findFirstTextNode(node) {
16563 				var walker;
16564 
16565 				if (node) {
16566 					walker = new TreeWalker(node, node);
16567 
16568 					for (node = walker.current(); node; node = walker.next()) {
16569 						if (node.nodeType === 3) {
16570 							return node;
16571 						}
16572 					}
16573 				}
16574 			};
16575 
16576 			// Removes the caret container for the specified node or all on the current document
16577 			function removeCaretContainer(node, move_caret) {
16578 				var child, rng;
16579 
16580 				if (!node) {
16581 					node = getParentCaretContainer(selection.getStart());
16582 
16583 					if (!node) {
16584 						while (node = dom.get(caretContainerId)) {
16585 							removeCaretContainer(node, false);
16586 						}
16587 					}
16588 				} else {
16589 					rng = selection.getRng(true);
16590 
16591 					if (isCaretContainerEmpty(node)) {
16592 						if (move_caret !== false) {
16593 							rng.setStartBefore(node);
16594 							rng.setEndBefore(node);
16595 						}
16596 
16597 						dom.remove(node);
16598 					} else {
16599 						child = findFirstTextNode(node);
16600 
16601 						if (child.nodeValue.charAt(0) === INVISIBLE_CHAR) {
16602 							child = child.deleteData(0, 1);
16603 						}
16604 
16605 						dom.remove(node, 1);
16606 					}
16607 
16608 					selection.setRng(rng);
16609 				}
16610 			};
16611 			
16612 			// Applies formatting to the caret postion
16613 			function applyCaretFormat() {
16614 				var rng, caretContainer, textNode, offset, bookmark, container, text;
16615 
16616 				rng = selection.getRng(true);
16617 				offset = rng.startOffset;
16618 				container = rng.startContainer;
16619 				text = container.nodeValue;
16620 
16621 				caretContainer = getParentCaretContainer(selection.getStart());
16622 				if (caretContainer) {
16623 					textNode = findFirstTextNode(caretContainer);
16624 				}
16625 
16626 				// Expand to word is caret is in the middle of a text node and the char before/after is a alpha numeric character
16627 				if (text && offset > 0 && offset < text.length && /\w/.test(text.charAt(offset)) && /\w/.test(text.charAt(offset - 1))) {
16628 					// Get bookmark of caret position
16629 					bookmark = selection.getBookmark();
16630 
16631 					// Collapse bookmark range (WebKit)
16632 					rng.collapse(true);
16633 
16634 					// Expand the range to the closest word and split it at those points
16635 					rng = expandRng(rng, get(name));
16636 					rng = rangeUtils.split(rng);
16637 
16638 					// Apply the format to the range
16639 					apply(name, vars, rng);
16640 
16641 					// Move selection back to caret position
16642 					selection.moveToBookmark(bookmark);
16643 				} else {
16644 					if (!caretContainer || textNode.nodeValue !== INVISIBLE_CHAR) {
16645 						caretContainer = createCaretContainer(true);
16646 						textNode = caretContainer.firstChild;
16647 
16648 						rng.insertNode(caretContainer);
16649 						offset = 1;
16650 
16651 						apply(name, vars, caretContainer);
16652 					} else {
16653 						apply(name, vars, caretContainer);
16654 					}
16655 
16656 					// Move selection to text node
16657 					selection.setCursorLocation(textNode, offset);
16658 				}
16659 			};
16660 
16661 			function removeCaretFormat() {
16662 				var rng = selection.getRng(true), container, offset, bookmark,
16663 					hasContentAfter, node, formatNode, parents = [], i, caretContainer;
16664 
16665 				container = rng.startContainer;
16666 				offset = rng.startOffset;
16667 				node = container;
16668 
16669 				if (container.nodeType == 3) {
16670 					if (offset != container.nodeValue.length || container.nodeValue === INVISIBLE_CHAR) {
16671 						hasContentAfter = true;
16672 					}
16673 
16674 					node = node.parentNode;
16675 				}
16676 
16677 				while (node) {
16678 					if (matchNode(node, name, vars)) {
16679 						formatNode = node;
16680 						break;
16681 					}
16682 
16683 					if (node.nextSibling) {
16684 						hasContentAfter = true;
16685 					}
16686 
16687 					parents.push(node);
16688 					node = node.parentNode;
16689 				}
16690 
16691 				// Node doesn't have the specified format
16692 				if (!formatNode) {
16693 					return;
16694 				}
16695 
16696 				// Is there contents after the caret then remove the format on the element
16697 				if (hasContentAfter) {
16698 					// Get bookmark of caret position
16699 					bookmark = selection.getBookmark();
16700 
16701 					// Collapse bookmark range (WebKit)
16702 					rng.collapse(true);
16703 
16704 					// Expand the range to the closest word and split it at those points
16705 					rng = expandRng(rng, get(name), true);
16706 					rng = rangeUtils.split(rng);
16707 
16708 					// Remove the format from the range
16709 					remove(name, vars, rng);
16710 
16711 					// Move selection back to caret position
16712 					selection.moveToBookmark(bookmark);
16713 				} else {
16714 					caretContainer = createCaretContainer();
16715 
16716 					node = caretContainer;
16717 					for (i = parents.length - 1; i >= 0; i--) {
16718 						node.appendChild(dom.clone(parents[i], false));
16719 						node = node.firstChild;
16720 					}
16721 
16722 					// Insert invisible character into inner most format element
16723 					node.appendChild(dom.doc.createTextNode(INVISIBLE_CHAR));
16724 					node = node.firstChild;
16725 
16726 					// Insert caret container after the formated node
16727 					dom.insertAfter(caretContainer, formatNode);
16728 
16729 					// Move selection to text node
16730 					selection.setCursorLocation(node, 1);
16731 				}
16732 			};
16733 
16734 			// Checks if the parent caret container node isn't empty if that is the case it
16735 			// will remove the bogus state on all children that isn't empty
16736 			function unmarkBogusCaretParents() {
16737 				var i, caretContainer, node;
16738 
16739 				caretContainer = getParentCaretContainer(selection.getStart());
16740 				if (caretContainer && !dom.isEmpty(caretContainer)) {
16741 					tinymce.walk(caretContainer, function(node) {
16742 						if (node.nodeType == 1 && node.id !== caretContainerId && !dom.isEmpty(node)) {
16743 							dom.setAttrib(node, 'data-mce-bogus', null);
16744 						}
16745 					}, 'childNodes');
16746 				}
16747 			};
16748 
16749 			// Only bind the caret events once
16750 			if (!self._hasCaretEvents) {
16751 				// Mark current caret container elements as bogus when getting the contents so we don't end up with empty elements
16752 				ed.onBeforeGetContent.addToTop(function() {
16753 					var nodes = [], i;
16754 
16755 					if (isCaretContainerEmpty(getParentCaretContainer(selection.getStart()), nodes)) {
16756 						// Mark children
16757 						i = nodes.length;
16758 						while (i--) {
16759 							dom.setAttrib(nodes[i], 'data-mce-bogus', '1');
16760 						}
16761 					}
16762 				});
16763 
16764 				// Remove caret container on mouse up and on key up
16765 				tinymce.each('onMouseUp onKeyUp'.split(' '), function(name) {
16766 					ed[name].addToTop(function() {
16767 						removeCaretContainer();
16768 						unmarkBogusCaretParents();
16769 					});
16770 				});
16771 
16772 				// Remove caret container on keydown and it's a backspace, enter or left/right arrow keys
16773 				ed.onKeyDown.addToTop(function(ed, e) {
16774 					var keyCode = e.keyCode;
16775 
16776 					if (keyCode == 8 || keyCode == 37 || keyCode == 39) {
16777 						removeCaretContainer(getParentCaretContainer(selection.getStart()));
16778 					}
16779 
16780 					unmarkBogusCaretParents();
16781 				});
16782 
16783 				// Remove bogus state if they got filled by contents using editor.selection.setContent
16784 				selection.onSetContent.add(unmarkBogusCaretParents);
16785 
16786 				self._hasCaretEvents = true;
16787 			}
16788 
16789 			// Do apply or remove caret format
16790 			if (type == "apply") {
16791 				applyCaretFormat();
16792 			} else {
16793 				removeCaretFormat();
16794 			}
16795 		};
16796 
16797 		function moveStart(rng) {
16798 			var container = rng.startContainer,
16799 					offset = rng.startOffset, isAtEndOfText,
16800 					walker, node, nodes, tmpNode;
16801 
16802 			// Convert text node into index if possible
16803 			if (container.nodeType == 3 && offset >= container.nodeValue.length) {
16804 				// Get the parent container location and walk from there
16805 				offset = nodeIndex(container);
16806 				container = container.parentNode;
16807 				isAtEndOfText = true;
16808 			}
16809 
16810 			// Move startContainer/startOffset in to a suitable node
16811 			if (container.nodeType == 1) {
16812 				nodes = container.childNodes;
16813 				container = nodes[Math.min(offset, nodes.length - 1)];
16814 				walker = new TreeWalker(container, dom.getParent(container, dom.isBlock));
16815 
16816 				// If offset is at end of the parent node walk to the next one
16817 				if (offset > nodes.length - 1 || isAtEndOfText)
16818 					walker.next();
16819 
16820 				for (node = walker.current(); node; node = walker.next()) {
16821 					if (node.nodeType == 3 && !isWhiteSpaceNode(node)) {
16822 						// IE has a "neat" feature where it moves the start node into the closest element
16823 						// we can avoid this by inserting an element before it and then remove it after we set the selection
16824 						tmpNode = dom.create('a', null, INVISIBLE_CHAR);
16825 						node.parentNode.insertBefore(tmpNode, node);
16826 
16827 						// Set selection and remove tmpNode
16828 						rng.setStart(node, 0);
16829 						selection.setRng(rng);
16830 						dom.remove(tmpNode);
16831 
16832 						return;
16833 					}
16834 				}
16835 			}
16836 		};
16837 	};
16838 })(tinymce);
16839 
16840 tinymce.onAddEditor.add(function(tinymce, ed) {
16841 	var filters, fontSizes, dom, settings = ed.settings;
16842 
16843 	function replaceWithSpan(node, styles) {
16844 		tinymce.each(styles, function(value, name) {
16845 			if (value)
16846 				dom.setStyle(node, name, value);
16847 		});
16848 
16849 		dom.rename(node, 'span');
16850 	};
16851 
16852 	function convert(editor, params) {
16853 		dom = editor.dom;
16854 
16855 		if (settings.convert_fonts_to_spans) {
16856 			tinymce.each(dom.select('font,u,strike', params.node), function(node) {
16857 				filters[node.nodeName.toLowerCase()](ed.dom, node);
16858 			});
16859 		}
16860 	};
16861 
16862 	if (settings.inline_styles) {
16863 		fontSizes = tinymce.explode(settings.font_size_legacy_values);
16864 
16865 		filters = {
16866 			font : function(dom, node) {
16867 				replaceWithSpan(node, {
16868 					backgroundColor : node.style.backgroundColor,
16869 					color : node.color,
16870 					fontFamily : node.face,
16871 					fontSize : fontSizes[parseInt(node.size, 10) - 1]
16872 				});
16873 			},
16874 
16875 			u : function(dom, node) {
16876 				replaceWithSpan(node, {
16877 					textDecoration : 'underline'
16878 				});
16879 			},
16880 
16881 			strike : function(dom, node) {
16882 				replaceWithSpan(node, {
16883 					textDecoration : 'line-through'
16884 				});
16885 			}
16886 		};
16887 
16888 		ed.onPreProcess.add(convert);
16889 		ed.onSetContent.add(convert);
16890 
16891 		ed.onInit.add(function() {
16892 			ed.selection.onSetContent.add(convert);
16893 		});
16894 	}
16895 });
16896 
16897 (function(tinymce) {
16898 	var TreeWalker = tinymce.dom.TreeWalker;
16899 
16900 	tinymce.EnterKey = function(editor) {
16901 		var dom = editor.dom, selection = editor.selection, settings = editor.settings, undoManager = editor.undoManager, nonEmptyElementsMap = editor.schema.getNonEmptyElements();
16902 
16903 		function handleEnterKey(evt) {
16904 			var rng = selection.getRng(true), tmpRng, editableRoot, container, offset, parentBlock, documentMode,
16905 				newBlock, fragment, containerBlock, parentBlockName, containerBlockName, newBlockName, isAfterLastNodeInContainer;
16906 
16907 			// Returns true if the block can be split into two blocks or not
16908 			function canSplitBlock(node) {
16909 				return node &&
16910 					dom.isBlock(node) &&
16911 					!/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) &&
16912 					!/^(fixed|absolute)/i.test(node.style.position) && 
16913 					dom.getContentEditable(node) !== "true";
16914 			};
16915 
16916 			// Renders empty block on IE
16917 			function renderBlockOnIE(block) {
16918 				var oldRng;
16919 
16920 				if (tinymce.isIE && dom.isBlock(block)) {
16921 					oldRng = selection.getRng();
16922 					block.appendChild(dom.create('span', null, '\u00a0'));
16923 					selection.select(block);
16924 					block.lastChild.outerHTML = '';
16925 					selection.setRng(oldRng);
16926 				}
16927 			};
16928 
16929 			// Remove the first empty inline element of the block so this: <p><b><em></em></b>x</p> becomes this: <p>x</p>
16930 			function trimInlineElementsOnLeftSideOfBlock(block) {
16931 				var node = block, firstChilds = [], i;
16932 
16933 				// Find inner most first child ex: <p><i><b>*</b></i></p>
16934 				while (node = node.firstChild) {
16935 					if (dom.isBlock(node)) {
16936 						return;
16937 					}
16938 
16939 					if (node.nodeType == 1 && !nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16940 						firstChilds.push(node);
16941 					}
16942 				}
16943 
16944 				i = firstChilds.length;
16945 				while (i--) {
16946 					node = firstChilds[i];
16947 					if (!node.hasChildNodes() || (node.firstChild == node.lastChild && node.firstChild.nodeValue === '')) {
16948 						dom.remove(node);
16949 					}
16950 				}
16951 			};
16952 			
16953 			// Moves the caret to a suitable position within the root for example in the first non pure whitespace text node or before an image
16954 			function moveToCaretPosition(root) {
16955 				var walker, node, rng, y, viewPort, lastNode = root, tempElm;
16956 
16957 				rng = dom.createRng();
16958 
16959 				if (root.hasChildNodes()) {
16960 					walker = new TreeWalker(root, root);
16961 
16962 					while (node = walker.current()) {
16963 						if (node.nodeType == 3) {
16964 							rng.setStart(node, 0);
16965 							rng.setEnd(node, 0);
16966 							break;
16967 						}
16968 
16969 						if (nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
16970 							rng.setStartBefore(node);
16971 							rng.setEndBefore(node);
16972 							break;
16973 						}
16974 
16975 						lastNode = node;
16976 						node = walker.next();
16977 					}
16978 
16979 					if (!node) {
16980 						rng.setStart(lastNode, 0);
16981 						rng.setEnd(lastNode, 0);
16982 					}
16983 				} else {
16984 					if (root.nodeName == 'BR') {
16985 						if (root.nextSibling && dom.isBlock(root.nextSibling)) {
16986 							// Trick on older IE versions to render the caret before the BR between two lists
16987 							if (!documentMode || documentMode < 9) {
16988 								tempElm = dom.create('br');
16989 								root.parentNode.insertBefore(tempElm, root);
16990 							}
16991 
16992 							rng.setStartBefore(root);
16993 							rng.setEndBefore(root);
16994 						} else {
16995 							rng.setStartAfter(root);
16996 							rng.setEndAfter(root);
16997 						}
16998 					} else {
16999 						rng.setStart(root, 0);
17000 						rng.setEnd(root, 0);
17001 					}
17002 				}
17003 
17004 				selection.setRng(rng);
17005 
17006 				// Remove tempElm created for old IE:s
17007 				dom.remove(tempElm);
17008 
17009 				viewPort = dom.getViewPort(editor.getWin());
17010 
17011 				// scrollIntoView seems to scroll the parent window in most browsers now including FF 3.0b4 so it's time to stop using it and do it our selfs
17012 				y = dom.getPos(root).y;
17013 				if (y < viewPort.y || y + 25 > viewPort.y + viewPort.h) {
17014 					editor.getWin().scrollTo(0, y < viewPort.y ? y : y - viewPort.h + 25); // Needs to be hardcoded to roughly one line of text if a huge text block is broken into two blocks
17015 				}
17016 			};
17017 
17018 			// Creates a new block element by cloning the current one or creating a new one if the name is specified
17019 			// This function will also copy any text formatting from the parent block and add it to the new one
17020 			function createNewBlock(name) {
17021 				var node = container, block, clonedNode, caretNode;
17022 
17023 				block = name || parentBlockName == "TABLE" ? dom.create(name || newBlockName) : parentBlock.cloneNode(false);
17024 				caretNode = block;
17025 
17026 				// Clone any parent styles
17027 				if (settings.keep_styles !== false) {
17028 					do {
17029 						if (/^(SPAN|STRONG|B|EM|I|FONT|STRIKE|U)$/.test(node.nodeName)) {
17030 							clonedNode = node.cloneNode(false);
17031 							dom.setAttrib(clonedNode, 'id', ''); // Remove ID since it needs to be document unique
17032 
17033 							if (block.hasChildNodes()) {
17034 								clonedNode.appendChild(block.firstChild);
17035 								block.appendChild(clonedNode);
17036 							} else {
17037 								caretNode = clonedNode;
17038 								block.appendChild(clonedNode);
17039 							}
17040 						}
17041 					} while (node = node.parentNode);
17042 				}
17043 
17044 				// BR is needed in empty blocks on non IE browsers
17045 				if (!tinymce.isIE) {
17046 					caretNode.innerHTML = '<br>';
17047 				}
17048 
17049 				return block;
17050 			};
17051 
17052 			// Returns true/false if the caret is at the start/end of the parent block element
17053 			function isCaretAtStartOrEndOfBlock(start) {
17054 				var walker, node, name;
17055 
17056 				// Caret is in the middle of a text node like "a|b"
17057 				if (container.nodeType == 3 && (start ? offset > 0 : offset < container.nodeValue.length)) {
17058 					return false;
17059 				}
17060 
17061 				// If after the last element in block node edge case for #5091
17062 				if (container.parentNode == parentBlock && isAfterLastNodeInContainer && !start) {
17063 					return true;
17064 				}
17065 
17066 				// If the caret if before the first element in parentBlock
17067 				if (start && container.nodeType == 1 && container == parentBlock.firstChild) {
17068 					return true;
17069 				}
17070 
17071 				// Caret can be before/after a table
17072 				if (container.nodeName === "TABLE" || (container.previousSibling && container.previousSibling.nodeName == "TABLE")) {
17073 					return (isAfterLastNodeInContainer && !start) || (!isAfterLastNodeInContainer && start);
17074 				}
17075 
17076 				// Walk the DOM and look for text nodes or non empty elements
17077 				walker = new TreeWalker(container, parentBlock);
17078 	
17079 				// If caret is in beginning or end of a text block then jump to the next/previous node
17080 				if (container.nodeType == 3) {
17081 					if (start && offset == 0) {
17082 						walker.prev();
17083 					} else if (!start && offset == container.nodeValue.length) {
17084 						walker.next();
17085 					}
17086 				}
17087 
17088 				while (node = walker.current()) {
17089 					if (node.nodeType === 1) {
17090 						// Ignore bogus elements
17091 						if (!node.getAttribute('data-mce-bogus')) {
17092 							// Keep empty elements like <img /> <input /> but not trailing br:s like <p>text|<br></p>
17093 							name = node.nodeName.toLowerCase();
17094 							if (nonEmptyElementsMap[name] && name !== 'br') {
17095 								return false;
17096 							}
17097 						}
17098 					} else if (node.nodeType === 3 && !/^[ \t\r\n]*$/.test(node.nodeValue)) {
17099 						return false;
17100 					}
17101 
17102 					if (start) {
17103 						walker.prev();
17104 					} else {
17105 						walker.next();
17106 					}
17107 				}
17108 
17109 				return true;
17110 			};
17111 
17112 			// Wraps any text nodes or inline elements in the specified forced root block name
17113 			function wrapSelfAndSiblingsInDefaultBlock(container, offset) {
17114 				var newBlock, parentBlock, startNode, node, next, blockName = newBlockName || 'P';
17115 
17116 				// Not in a block element or in a table cell or caption
17117 				parentBlock = dom.getParent(container, dom.isBlock);
17118 				if (!parentBlock || !canSplitBlock(parentBlock)) {
17119 					parentBlock = parentBlock || editableRoot;
17120 
17121 					if (!parentBlock.hasChildNodes()) {
17122 						newBlock = dom.create(blockName);
17123 						parentBlock.appendChild(newBlock);
17124 						rng.setStart(newBlock, 0);
17125 						rng.setEnd(newBlock, 0);
17126 						return newBlock;
17127 					}
17128 
17129 					// Find parent that is the first child of parentBlock
17130 					node = container;
17131 					while (node.parentNode != parentBlock) {
17132 						node = node.parentNode;
17133 					}
17134 
17135 					// Loop left to find start node start wrapping at
17136 					while (node && !dom.isBlock(node)) {
17137 						startNode = node;
17138 						node = node.previousSibling;
17139 					}
17140 
17141 					if (startNode) {
17142 						newBlock = dom.create(blockName);
17143 						startNode.parentNode.insertBefore(newBlock, startNode);
17144 
17145 						// Start wrapping until we hit a block
17146 						node = startNode;
17147 						while (node && !dom.isBlock(node)) {
17148 							next = node.nextSibling;
17149 							newBlock.appendChild(node);
17150 							node = next;
17151 						}
17152 
17153 						// Restore range to it's past location
17154 						rng.setStart(container, offset);
17155 						rng.setEnd(container, offset);
17156 					}
17157 				}
17158 
17159 				return container;
17160 			};
17161 
17162 			// Inserts a block or br before/after or in the middle of a split list of the LI is empty
17163 			function handleEmptyListItem() {
17164 				function isFirstOrLastLi(first) {
17165 					var node = containerBlock[first ? 'firstChild' : 'lastChild'];
17166 
17167 					// Find first/last element since there might be whitespace there
17168 					while (node) {
17169 						if (node.nodeType == 1) {
17170 							break;
17171 						}
17172 
17173 						node = node[first ? 'nextSibling' : 'previousSibling'];
17174 					}
17175 
17176 					return node === parentBlock;
17177 				};
17178 
17179 				newBlock = newBlockName ? createNewBlock(newBlockName) : dom.create('BR');
17180 
17181 				if (isFirstOrLastLi(true) && isFirstOrLastLi()) {
17182 					// Is first and last list item then replace the OL/UL with a text block
17183 					dom.replace(newBlock, containerBlock);
17184 				} else if (isFirstOrLastLi(true)) {
17185 					// First LI in list then remove LI and add text block before list
17186 					containerBlock.parentNode.insertBefore(newBlock, containerBlock);
17187 				} else if (isFirstOrLastLi()) {
17188 					// Last LI in list then temove LI and add text block after list
17189 					dom.insertAfter(newBlock, containerBlock);
17190 					renderBlockOnIE(newBlock);
17191 				} else {
17192 					// Middle LI in list the split the list and insert a text block in the middle
17193 					// Extract after fragment and insert it after the current block
17194 					tmpRng = rng.cloneRange();
17195 					tmpRng.setStartAfter(parentBlock);
17196 					tmpRng.setEndAfter(containerBlock);
17197 					fragment = tmpRng.extractContents();
17198 					dom.insertAfter(fragment, containerBlock);
17199 					dom.insertAfter(newBlock, containerBlock);
17200 				}
17201 
17202 				dom.remove(parentBlock);
17203 				moveToCaretPosition(newBlock);
17204 				undoManager.add();
17205 			};
17206 
17207 			// Walks the parent block to the right and look for BR elements
17208 			function hasRightSideBr() {
17209 				var walker = new TreeWalker(container, parentBlock), node;
17210 
17211 				while (node = walker.current()) {
17212 					if (node.nodeName == 'BR') {
17213 						return true;
17214 					}
17215 
17216 					node = walker.next();
17217 				}
17218 			}
17219 			
17220 			// Inserts a BR element if the forced_root_block option is set to false or empty string
17221 			function insertBr() {
17222 				var brElm, extraBr;
17223 
17224 				if (container && container.nodeType == 3 && offset >= container.nodeValue.length) {
17225 					// Insert extra BR element at the end block elements
17226 					if (!tinymce.isIE && !hasRightSideBr()) {
17227 						brElm = dom.create('br')
17228 						rng.insertNode(brElm);
17229 						rng.setStartAfter(brElm);
17230 						rng.setEndAfter(brElm);
17231 						extraBr = true;
17232 					}
17233 				}
17234 
17235 				brElm = dom.create('br');
17236 				rng.insertNode(brElm);
17237 
17238 				// Rendering modes below IE8 doesn't display BR elements in PRE unless we have a \n before it
17239 				if (tinymce.isIE && parentBlockName == 'PRE' && (!documentMode || documentMode < 8)) {
17240 					brElm.parentNode.insertBefore(dom.doc.createTextNode('\r'), brElm);
17241 				}
17242 
17243 				if (!extraBr) {
17244 					rng.setStartAfter(brElm);
17245 					rng.setEndAfter(brElm);
17246 				} else {
17247 					rng.setStartBefore(brElm);
17248 					rng.setEndBefore(brElm);
17249 				}
17250 
17251 				selection.setRng(rng);
17252 				undoManager.add();
17253 			};
17254 
17255 			// Trims any linebreaks at the beginning of node user for example when pressing enter in a PRE element
17256 			function trimLeadingLineBreaks(node) {
17257 				do {
17258 					if (node.nodeType === 3) {
17259 						node.nodeValue = node.nodeValue.replace(/^[\r\n]+/, '');
17260 					}
17261 
17262 					node = node.firstChild;
17263 				} while (node);
17264 			};
17265 
17266 			function getEditableRoot(node) {
17267 				var root = dom.getRoot(), parent, editableRoot;
17268 
17269 				// Get all parents until we hit a non editable parent or the root
17270 				parent = node;
17271 				while (parent !== root && dom.getContentEditable(parent) !== "false") {
17272 					if (dom.getContentEditable(parent) === "true") {
17273 						editableRoot = parent;
17274 					}
17275 
17276 					parent = parent.parentNode;
17277 				}
17278 				
17279 				return parent !== root ? editableRoot : root;
17280 			};
17281 
17282 			// Adds a BR at the end of blocks that only contains an IMG or INPUT since these might be floated and then they won't expand the block
17283 			function addBrToBlockIfNeeded(block) {
17284 				var lastChild;
17285 
17286 				// IE will render the blocks correctly other browsers needs a BR
17287 				if (!tinymce.isIE) {
17288 					block.normalize(); // Remove empty text nodes that got left behind by the extract
17289 
17290 					// Check if the block is empty or contains a floated last child
17291 					lastChild = block.lastChild;
17292 					if (!lastChild || (/^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true)))) {
17293 						dom.add(block, 'br');
17294 					}
17295 				}
17296 			};
17297 
17298 			// Delete any selected contents
17299 			if (!rng.collapsed) {
17300 				editor.execCommand('Delete');
17301 				return;
17302 			}
17303 
17304 			// Event is blocked by some other handler for example the lists plugin
17305 			if (evt.isDefaultPrevented()) {
17306 				return;
17307 			}
17308 
17309 			// Setup range items and newBlockName
17310 			container = rng.startContainer;
17311 			offset = rng.startOffset;
17312 			newBlockName = settings.forced_root_block;
17313 			newBlockName = newBlockName ? newBlockName.toUpperCase() : '';
17314 			documentMode = dom.doc.documentMode;
17315 
17316 			// Resolve node index
17317 			if (container.nodeType == 1 && container.hasChildNodes()) {
17318 				isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
17319 				container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
17320 				if (isAfterLastNodeInContainer && container.nodeType == 3) {
17321 					offset = container.nodeValue.length;
17322 				} else {
17323 					offset = 0;
17324 				}
17325 			}
17326 
17327 			// Get editable root node normaly the body element but sometimes a div or span
17328 			editableRoot = getEditableRoot(container);
17329 
17330 			// If there is no editable root then enter is done inside a contentEditable false element
17331 			if (!editableRoot) {
17332 				return;
17333 			}
17334 
17335 			undoManager.beforeChange();
17336 
17337 			// If editable root isn't block nor the root of the editor
17338 			if (!dom.isBlock(editableRoot) && editableRoot != dom.getRoot()) {
17339 				if (!newBlockName || evt.shiftKey) {
17340 					insertBr();
17341 				}
17342 
17343 				return;
17344 			}
17345 
17346 			// Wrap the current node and it's sibling in a default block if it's needed.
17347 			// for example this <td>text|<b>text2</b></td> will become this <td><p>text|<b>text2</p></b></td>
17348 			// This won't happen if root blocks are disabled or the shiftKey is pressed
17349 			if ((newBlockName && !evt.shiftKey) || (!newBlockName && evt.shiftKey)) {
17350 				container = wrapSelfAndSiblingsInDefaultBlock(container, offset);
17351 			}
17352 
17353 			// Find parent block and setup empty block paddings
17354 			parentBlock = dom.getParent(container, dom.isBlock);
17355 			containerBlock = parentBlock ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
17356 
17357 			// Setup block names
17358 			parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17359 			containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : ''; // IE < 9 & HTML5
17360 
17361 			// Handle enter inside an empty list item
17362 			if (parentBlockName == 'LI' && dom.isEmpty(parentBlock)) {
17363 				// Let the list plugin or browser handle nested lists for now
17364 				if (/^(UL|OL|LI)$/.test(containerBlock.parentNode.nodeName)) {
17365 					return false;
17366 				}
17367 
17368 				handleEmptyListItem();
17369 				return;
17370 			}
17371 
17372 			// Don't split PRE tags but insert a BR instead easier when writing code samples etc
17373 			if (parentBlockName == 'PRE' && settings.br_in_pre !== false) {
17374 				if (!evt.shiftKey) {
17375 					insertBr();
17376 					return;
17377 				}
17378 			} else {
17379 				// If no root block is configured then insert a BR by default or if the shiftKey is pressed
17380 				if ((!newBlockName && !evt.shiftKey && parentBlockName != 'LI') || (newBlockName && evt.shiftKey)) {
17381 					insertBr();
17382 					return;
17383 				}
17384 			}
17385 
17386 			// Default block name if it's not configured
17387 			newBlockName = newBlockName || 'P';
17388 
17389 			// Insert new block before/after the parent block depending on caret location
17390 			if (isCaretAtStartOrEndOfBlock()) {
17391 				// If the caret is at the end of a header we produce a P tag after it similar to Word unless we are in a hgroup
17392 				if (/^(H[1-6]|PRE)$/.test(parentBlockName) && containerBlockName != 'HGROUP') {
17393 					newBlock = createNewBlock(newBlockName);
17394 				} else {
17395 					newBlock = createNewBlock();
17396 				}
17397 
17398 				// Split the current container block element if enter is pressed inside an empty inner block element
17399 				if (settings.end_container_on_empty_block && canSplitBlock(containerBlock) && dom.isEmpty(parentBlock)) {
17400 					// Split container block for example a BLOCKQUOTE at the current blockParent location for example a P
17401 					newBlock = dom.split(containerBlock, parentBlock);
17402 				} else {
17403 					dom.insertAfter(newBlock, parentBlock);
17404 				}
17405 
17406 				moveToCaretPosition(newBlock);
17407 			} else if (isCaretAtStartOrEndOfBlock(true)) {
17408 				// Insert new block before
17409 				newBlock = parentBlock.parentNode.insertBefore(createNewBlock(), parentBlock);
17410 				renderBlockOnIE(newBlock);
17411 			} else {
17412 				// Extract after fragment and insert it after the current block
17413 				tmpRng = rng.cloneRange();
17414 				tmpRng.setEndAfter(parentBlock);
17415 				fragment = tmpRng.extractContents();
17416 				trimLeadingLineBreaks(fragment);
17417 				newBlock = fragment.firstChild;
17418 				dom.insertAfter(fragment, parentBlock);
17419 				trimInlineElementsOnLeftSideOfBlock(newBlock);
17420 				addBrToBlockIfNeeded(parentBlock);
17421 				moveToCaretPosition(newBlock);
17422 			}
17423 
17424 			dom.setAttrib(newBlock, 'id', ''); // Remove ID since it needs to be document unique
17425 			undoManager.add();
17426 		}
17427 
17428 		editor.onKeyDown.add(function(ed, evt) {
17429 			if (evt.keyCode == 13) {
17430 				if (handleEnterKey(evt) !== false) {
17431 					evt.preventDefault();
17432 				}
17433 			}
17434 		});
17435 	};
17436 })(tinymce);
17437 
17438